
// initialize menus & dialogs, etc.
// for most of the prefs dialogs, all code resides here in the form of
// event handlers & validators
// other non-viewer dialogs are at least validated enough that they won't crash
// viewer dialogs are not commonly used, so they are initialized on demand

#include "wxvbam.h"

#include <typeinfo>
#include <stdexcept>
#include <algorithm>

#include <wx/stockitem.h>
#include <wx/spinctrl.h>
#include <wx/clrpicker.h>
#include <wx/filepicker.h>
#include <wx/dir.h>
#include <wx/tokenzr.h>
#include <wx/checkedlistctrl.h>
#include <wx/progdlg.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>

#include "../common/ConfigManager.h"
#include "../gba/CheatSearch.h"

// The program icon, in case it's missing from .xrc (MSW gets it from .rc file)
#if !defined(__WXMSW__) && !defined(__WXPM__)
// ImageMagick makes the name wxvbam, but wx expects wxvbam_xpm
#define wxvbam wxvbam_xpm
const
#include "xrc/vbam.xpm"
#undef wxvbam
#endif

// this is supposed to happen automatically if a parent is marked recursive
// but some dialogs don't do it (propertydialog?)
// so go ahead and mark all dialogs for fully recursive validation
static void mark_recursive(wxWindowBase* w)
{
	w->SetExtraStyle(w->GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY);
	wxWindowList l = w->GetChildren();

	for (wxWindowList::iterator ch = l.begin(); ch != l.end(); ch++)
		mark_recursive(*ch);
}

#if (wxMAJOR_VERSION < 3)
#define GetXRCDialog(n) \
    wxStaticCast(wxGetApp().frame->FindWindow(XRCID(n)), wxDialog)
#else
#define GetXRCDialog(n) \
    wxStaticCast(wxGetApp().frame->FindWindowByName(n), wxDialog)
#endif

// Event handlers must be methods of wxEvtHandler-derived objects

// manage the network link dialog
#ifndef NO_LINK
static class NetLink_t : public wxEvtHandler
{
public:
	wxDialog* dlg;
	int n_players;
	bool server;
	NetLink_t() : n_players(2), server(false) {}
	wxButton* okb;
	void ServerOKButton(wxCommandEvent &ev)
	{
		okb->SetLabel(_("Start!"));
	}
	void ClientOKButton(wxCommandEvent &ev)
	{
		okb->SetLabel(_("Connect"));
	}
	// attached to OK, so skip when OK
	void NetConnect(wxCommandEvent &ev)
	{
		static const int length = 256;

		if (!dlg->Validate() || !dlg->TransferDataFromWindow())
			return;

		if (!server)
		{
			bool valid = SetLinkServerHost(gopts.link_host.mb_str());

			if (!valid)
			{
				wxMessageBox(_("You must enter a valid host name"),
				             _("Host name invalid"), wxICON_ERROR | wxOK);
				return;
			}
		}

		linkNumPlayers = n_players;
		update_opts(); // save fast flag and client host
		// Close any previous link
		CloseLink();
		wxString connmsg;
		wxString title;
		SetLinkTimeout(linkTimeout);
		EnableSpeedHacks(linkHacks);
		EnableLinkServer(server, linkNumPlayers - 1);

		if (server)
		{
			char host[length];
			GetLinkServerHost(host, length);
			title.Printf(_("Waiting for clients..."));
			connmsg.Printf(_("Server IP address is: %s\n"), wxString(host, wxConvLibc).c_str());
		}
		else
		{
			title.Printf(_("Waiting for connection..."));
			connmsg.Printf(_("Connecting to %s\n"), gopts.link_host.c_str());
		}

		// Init link
		MainFrame* mf = wxGetApp().frame;
		ConnectionState state = InitLink(mf->GetConfiguredLinkMode());

		// Display a progress dialog while the connection is establishing
		if (state == LINK_NEEDS_UPDATE)
		{
			wxProgressDialog pdlg(title, connmsg,
			                      100, dlg, wxPD_APP_MODAL | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME);

			while (state == LINK_NEEDS_UPDATE)
			{
				// Ask the core for updates
				char message[length];
				state = ConnectLinkUpdate(message, length);
				connmsg = wxString(message, wxConvLibc);

				// Does the user want to abort?
				if (!pdlg.Pulse(connmsg))
				{
					state = LINK_ABORT;
				}
			}
		}

		// The user canceled the connection attempt
		if (state == LINK_ABORT)
		{
			CloseLink();
		}

		// Something failed during init
		if (state == LINK_ERROR)
		{
			CloseLink();
			wxLogError(_("Error occurred.\nPlease try again."));
		}

		if (GetLinkMode() != LINK_DISCONNECTED)
		{
			connmsg.Replace(wxT("\n"), wxT(" "));
			systemScreenMessage(connmsg);
			ev.Skip(); // all OK
		}
	}
} net_link_handler;
#endif

// manage the cheat list dialog
static class CheatList_t : public wxEvtHandler
{
public:
	wxDialog* dlg;
	wxCheckedListCtrl* list;
	wxListItem item0, item1;
	int col1minw;
	wxString cheatdir, cheatfn, deffn;
	bool isgb;
	bool* dirty;

	// add/edit dialog
	wxString ce_desc;
	wxString ce_codes;
	wxChoice* ce_type_ch;
	wxControl* ce_codes_tc;
	int ce_type;

	void Reload()
	{
		list->DeleteAllItems();
		Reload(0);
	}

	void Reload(int start)
	{
		if (isgb)
		{
			for (int i = start; i < gbCheatNumber; i++)
			{
				item0.SetId(i);
				item0.SetText(wxString(gbCheatList[i].cheatCode, wxConvLibc));
				list->InsertItem(item0);
				item1.SetId(i);
				item1.SetText(wxString(gbCheatList[i].cheatDesc, wxConvUTF8));
				list->SetItem(item1);
				list->Check(i, gbCheatList[i].enabled);
			}
		}
		else
		{
			for (int i = start; i < cheatsNumber; i++)
			{
				item0.SetId(i);
				item0.SetText(wxString(cheatsList[i].codestring, wxConvLibc));
				list->InsertItem(item0);
				item1.SetId(i);
				item1.SetText(wxString(cheatsList[i].desc, wxConvUTF8));
				list->SetItem(item1);
				list->Check(i, cheatsList[i].enabled);
			}
		}

		AdjustDescWidth();
	}

	void Tool(wxCommandEvent &ev)
	{
		switch (ev.GetId())
		{
		case wxID_OPEN:
		{
			wxFileDialog subdlg(dlg, _("Select cheat file"), cheatdir, cheatfn,
			                    _("VBA cheat lists (*.clt)|*.clt|CHT cheat lists (*.cht)|*.cht"),
			                    wxFD_OPEN | wxFD_FILE_MUST_EXIST);
			int ret = subdlg.ShowModal();
			cheatdir = subdlg.GetDirectory();
			cheatfn = subdlg.GetPath();

			if (ret != wxID_OK)
				break;

			bool cld;

			if (isgb)
				cld = gbCheatsLoadCheatList(cheatfn.mb_fn_str());
			else
			{
				if (cheatfn.EndsWith(wxT(".clt")))
				{
					cld = cheatsLoadCheatList(cheatfn.mb_fn_str());

					if (cld)
					{
						*dirty = cheatfn != deffn;
						systemScreenMessage(_("Loaded cheats"));
					}
					else
						*dirty = true; // attempted load always clears
				}
				else
				{
					// cht format
					wxFileInputStream input(cheatfn);
					wxTextInputStream text(input, wxT("\x09"), wxConvUTF8);
					wxString cheat_desc = wxT("");

					while (input.IsOk() && !input.Eof())
					{
						wxString line = text.ReadLine().Trim();

						if (line.Contains(wxT("[")) && !line.Contains(wxT("=")))
						{
							cheat_desc = line.AfterFirst('[').BeforeLast(']');
						}

						if (line.Contains(wxT("=")) && cheat_desc != wxT("GameInfo"))
						{
							while ((input.IsOk() && !input.Eof()) && (line.EndsWith(wxT(";")) || line.EndsWith(wxT(","))))
							{
								line = line + text.ReadLine().Trim();
							}

							ParseChtLine(cheat_desc, line);
						}
					}

					*dirty = true;
				}
			}

			Reload();
		}
		break;

		case wxID_SAVE:
		{
			wxFileDialog subdlg(dlg, _("Select cheat file"), cheatdir,
			                    cheatfn, _("VBA cheat lists (*.clt)|*.clt"),
			                    wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
			int ret = subdlg.ShowModal();
			cheatdir = subdlg.GetDirectory();
			cheatfn = subdlg.GetPath();

			if (ret != wxID_OK)
				break;

			// note that there is no way to test for succes of save
			if (isgb)
				gbCheatsSaveCheatList(cheatfn.mb_fn_str());
			else
				cheatsSaveCheatList(cheatfn.mb_fn_str());

			if (cheatfn == deffn)
				*dirty = false;

			systemScreenMessage(_("Saved cheats"));
		}
		break;

		case wxID_ADD:
		{
			int ncheats = isgb ? gbCheatNumber : cheatsNumber;
			ce_codes = wxEmptyString;
			wxDialog* subdlg = GetXRCDialog("CheatEdit");
			dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);

			if (gopts.keep_on_top)
				subdlg->SetWindowStyle(subdlg->GetWindowStyle() | wxSTAY_ON_TOP);
			else
				subdlg->SetWindowStyle(subdlg->GetWindowStyle() & ~wxSTAY_ON_TOP);

			subdlg->ShowModal();
			AddCheat();
			Reload(ncheats);
		}
		break;

		case wxID_REMOVE:
		{
			bool asked = false, restore;

			for (int i = list->GetItemCount() - 1; i >= 0; i--)
				if (list->GetItemState(i, wxLIST_STATE_SELECTED))
				{
					list->DeleteItem(i);

					if (isgb)
						gbCheatRemove(i);
					else
					{
						if (!asked)
						{
							asked = true;
							restore = wxMessageBox(_("Restore old values?"),
							                       _("Removing cheats"),
							                       wxYES_NO | wxICON_QUESTION) == wxYES;
						}

						cheatsDelete(i, restore);
					}
				}
		}
		break;

		case wxID_CLEAR:
			if (isgb)
			{
				if (gbCheatNumber)
				{
					*dirty = true;
					gbCheatRemoveAll();
				}
			}
			else
			{
				if (cheatsNumber)
				{
					bool restore = wxMessageBox(_("Restore old values?"),
					                            _("Removing cheats"),
					                            wxYES_NO | wxICON_QUESTION) == wxYES;
					*dirty = true;
					cheatsDeleteAll(restore);
				}
			}

			Reload();
			break;

		case wxID_SELECTALL:
			// FIXME: probably ought to limit to selected items if any items
			// are selected
			*dirty = true;

			if (isgb)
			{
				int i;

				for (i = 0; i < gbCheatNumber; i++)
					if (!gbCheatList[i].enabled)
						break;

				if (i < gbCheatNumber)
					for (; i < gbCheatNumber; i++)
					{
						gbCheatEnable(i);
						list->Check(i, true);
					}
				else
					for (i = 0; i < gbCheatNumber; i++)
					{
						gbCheatDisable(i);
						list->Check(i, false);
					}
			}
			else
			{
				int i;

				for (i = 0; i < cheatsNumber; i++)
					if (!cheatsList[i].enabled)
						break;

				if (i < cheatsNumber)
					for (; i < cheatsNumber; i++)
					{
						cheatsEnable(i);
						list->Check(i, true);
					}
				else
					for (i = 0; i < cheatsNumber; i++)
					{
						cheatsDisable(i);
						list->Check(i, false);
					}
			}

			break;
		}
	}

	void Check(wxListEvent &ev)
	{
		int ch = ev.GetIndex();

		if (isgb)
		{
			if (!gbCheatList[ch].enabled)
			{
				gbCheatEnable(ev.GetIndex());
				*dirty = true;
			}
		}
		else
		{
			if (!cheatsList[ch].enabled)
			{
				cheatsEnable(ev.GetIndex());
				*dirty = true;
			}
		}
	}

	void UnCheck(wxListEvent &ev)
	{
		int ch = ev.GetIndex();

		if (isgb)
		{
			if (gbCheatList[ch].enabled)
			{
				gbCheatDisable(ev.GetIndex());
				*dirty = true;
			}
		}
		else
		{
			if (cheatsList[ch].enabled)
			{
				cheatsDisable(ev.GetIndex());
				*dirty = true;
			}
		}
	}

	void AddCheat()
	{
		wxStringTokenizer tk(ce_codes.MakeUpper());

		while (tk.HasMoreTokens())
		{
			wxString tok = tk.GetNextToken();

			if (isgb)
			{
				if (!ce_type)
					gbAddGsCheat(tok.mb_str(), ce_desc.mb_str());
				else
					gbAddGgCheat(tok.mb_str(), ce_desc.mb_str());
			}
			else
			{
				// Flashcart CHT format
				if (tok.Contains(wxT("=")))
				{
					ParseChtLine(ce_desc, tok);
				}
				// Generic Code
				else if (tok.Contains(wxT(":")))
					cheatsAddCheatCode(tok.mb_str(), ce_desc.mb_str());
				// following determination of type by lengths is
				// same used by win32 and gtk code
				// and like win32/gtk code, user-chosen fmt is ignored
				else if (tok.size() == 12)
				{
					tok = tok.substr(0, 8) + wxT(' ') + tok.substr(8);
					cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str());
				}
				else if (tok.size() == 16)
					// not sure why 1-tok is !v3 and 2-tok is v3..
					cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), false);
				// CBA codes are assumed to be N+4, and anything else
				// is assumed to be GSA v3 (although I assume the
				// actual formats should be 8+4 and 8+8)
				else
				{
					if (!tk.HasMoreTokens())
					{
						// throw an error appropriate to chosen type
						if (ce_type == 1) // GSA
							cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), false);
						else
							cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str());
					}
					else
					{
						wxString tok2 = tk.GetNextToken();

						if (tok2.size() == 4)
						{
							tok += wxT(' ') + tok2;
							cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str());
						}
						else
						{
							tok += tok2;
							cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), true);
						}
					}
				}
			}
		}
	}

	void ParseChtLine(wxString desc, wxString tok);

	void Edit(wxListEvent &ev)
	{
		int id = ev.GetIndex();
		// GetItem() followed by GetText doesn't work, so retrieve from
		// source
		wxString odesc, ocode;
		bool ochecked;
		int otype;

		if (isgb)
		{
			ochecked = gbCheatList[id].enabled;
			ce_codes = ocode = wxString(gbCheatList[id].cheatCode, wxConvLibc);
			ce_desc = odesc = wxString(gbCheatList[id].cheatDesc, wxConvUTF8);

			if (ce_codes.find(wxT('-')) == wxString::npos)
				otype = ce_type = 0;
			else
				otype = ce_type = 1;
		}
		else
		{
			ochecked = cheatsList[id].enabled;
			ce_codes = ocode = wxString(cheatsList[id].codestring, wxConvLibc);
			ce_desc = odesc = wxString(cheatsList[id].desc, wxConvUTF8);

			if (ce_codes.find(wxT(':')) != wxString::npos)
				otype = ce_type = 0;
			else if (ce_codes.find(wxT(' ')) == wxString::npos)
				otype = ce_type = 1;
			else
				otype = ce_type = 2;
		}

		wxDialog* subdlg = GetXRCDialog("CheatEdit");
		dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);

		if (gopts.keep_on_top)
			subdlg->SetWindowStyle(subdlg->GetWindowStyle() | wxSTAY_ON_TOP);
		else
			subdlg->SetWindowStyle(subdlg->GetWindowStyle() & ~wxSTAY_ON_TOP);

		if (subdlg->ShowModal() != wxID_OK)
			return;

		if (otype != ce_type || ocode != ce_codes)
		{
			// vba core certainly doesn't make this easy
			// there is no "change" function, so the only way to retain
			// the old order is to delete this and all subsequent items, and
			// then re-add them
			// The MFC code got around this by not even supporting edits on
			// gba codes (which have order dependencies) and just forcing
			// edited codes to the rear on gb codes.
			// It might be safest to only support desc edits, and force the
			// user to re-enter codes to change them
			int ncodes = isgb ? gbCheatNumber : cheatsNumber;

			if (ncodes > id + 1)
			{
				wxString codes[MAX_CHEATS];
				wxString descs[MAX_CHEATS];
				bool checked[MAX_CHEATS];
				bool v3[MAX_CHEATS];

				for (int i = id + 1; i < ncodes; i++)
				{
					codes[i - id - 1] = wxString(isgb ?
					                             gbCheatList[i].cheatCode :
					                             cheatsList[i].codestring,
					                             wxConvLibc);
					descs[i - id - 1] = wxString(isgb ?
					                             gbCheatList[i].cheatDesc :
					                             cheatsList[i].desc,
					                             wxConvUTF8);
					checked[i - id - 1] = isgb ? gbCheatList[i].enabled :
					                      cheatsList[i].enabled;
					v3[i - id - 1] = isgb ? false : cheatsList[i].code == 257;
				}

				for (int i = ncodes - 1; i >= id; i--)
				{
					list->DeleteItem(i);

					if (isgb)
						gbCheatRemove(i);
					else
						cheatsDelete(i, cheatsList[i].enabled);
				}

				AddCheat();

				if (!ochecked)
				{
					if (isgb)
						gbCheatDisable(id);
					else
						cheatsDisable(id);
				}

				for (int i = id + 1; i < ncodes; i++)
				{
					ce_codes = codes[i - id - 1];
					ce_desc = descs[i - id - 1];

					if (isgb)
					{
						if (ce_codes.find(wxT('-')) == wxString::npos)
							ce_type = 0;
						else
							ce_type = 1;
					}
					else
					{
						if (ce_codes.find(wxT(':')) != wxString::npos)
							ce_type = 0;
						else if (ce_codes.find(wxT(' ')) == wxString::npos)
						{
							ce_type = 1;

							if (v3[i - id - 1])
								ce_codes.insert(8, 1, wxT(' '));
						}
						else
						{
							ce_type = 2;
						}
					}

					AddCheat();

					if (!checked[i - id - 1])
					{
						if (isgb)
							gbCheatDisable(i);
						else
							cheatsDisable(i);
					}
				}
			}
			else
			{
				list->DeleteItem(id);

				if (isgb)
					gbCheatRemove(id);
				else
					cheatsDelete(id, cheatsList[id].enabled);

				AddCheat();

				if (!ochecked)
				{
					if (isgb)
						gbCheatDisable(id);
					else
						cheatsDisable(id);
				}
			}

			Reload(id);
		}
		else if (ce_desc != odesc)
		{
			*dirty = true;
			char* p = isgb ? gbCheatList[id].cheatDesc : cheatsList[id].desc;
			strncpy(p, ce_desc.mb_str(), sizeof(cheatsList[0].desc));
			p[sizeof(cheatsList[0].desc) - 1] = 0;
			item1.SetId(id);
			item1.SetText(wxString(p, wxConvUTF8));
			list->SetItem(item1);
		}
	}

	void AdjustDescWidth()
	{
		// why is it so hard to get an accurate measurement out of wx?
		// on msw, wxLIST_AUTOSIZE might actually be accurate.  On wxGTK,
		// and probably wxMAC (both of which use generic impl) wrong
		// font is used both for rendering (col 0's font) and for
		// wxLIST_AUTOSIZE calculation (the widget's font).
		// The only way to defeat this is to calculate size manually
		// Instead, this just allows user to set max size, and retains
		// it.
		int ow = list->GetColumnWidth(1);
		list->SetColumnWidth(1, wxLIST_AUTOSIZE);
		int cw = list->GetColumnWidth(1);
		// subtracted in renderer from width avail for text
		// but not added in wxLIST_AUTOSIZE
		cw += 8;

		if (cw < col1minw)
			cw = col1minw;

		if (cw < ow)
			cw = ow;

		list->SetColumnWidth(1, cw);
	}
} cheat_list_handler;

void CheatList_t::ParseChtLine(wxString desc, wxString tok)
{
	wxString cheat_opt = tok.BeforeFirst(wxT('='));
	wxString cheat_set = tok.AfterFirst(wxT('='));
	wxStringTokenizer addr_tk(cheat_set.MakeUpper(), wxT(";"));

	while (addr_tk.HasMoreTokens())
	{
		wxString addr_token = addr_tk.GetNextToken();
		wxString cheat_addr = addr_token.BeforeFirst(wxT(','));
		wxString values = addr_token.AfterFirst(wxT(','));
		wxString cheat_desc = desc + wxT(":") + cheat_opt;
		wxString cheat_line;
		wxString cheat_value;
		u32 address = 0;
		u32 value = 0;
		sscanf(cheat_addr.mb_str(), "%8x", &address);

		if (address < 0x40000)
			address += 0x2000000;
		else
			address += 0x3000000 - 0x40000;

		wxStringTokenizer value_tk(values.MakeUpper(), wxT(","));

		while (value_tk.HasMoreTokens())
		{
			wxString value_token = value_tk.GetNextToken();
			sscanf(value_token.mb_str(), "%2x", &value);
			cheat_line.Printf(wxT("%08X"), address);
			cheat_value.Printf(wxT("%02X"), value);
			cheat_line = cheat_line + wxT(":") + cheat_value;
			cheatsAddCheatCode(cheat_line.mb_str(), cheat_desc.mb_str());
			address++;
		}
	}
}

// onshow handler for above, in the form of an overzealous validator
class CheatListFill : public wxValidator
{
public:
	CheatListFill() : wxValidator() {}
	CheatListFill(const CheatListFill &e) : wxValidator() {}
	wxObject* Clone() const { return new CheatListFill(*this); }
	bool TransferFromWindow() { return true; }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		CheatList_t &clh = cheat_list_handler;
		GameArea* panel = wxGetApp().frame->GetPanel();
		clh.isgb = panel->game_type() == IMAGE_GB;
		clh.dirty = &panel->cheats_dirty;
		clh.cheatfn = panel->game_name() + wxT(".clt");
		clh.cheatdir = panel->game_dir();
		clh.deffn = wxFileName(clh.cheatdir, clh.cheatfn).GetFullPath();
		clh.Reload();
		clh.ce_desc = wxEmptyString;
		wxChoice* ch = clh.ce_type_ch;
		ch->Clear();

		if (clh.isgb)
		{
			ch->Append(_("GameShark"));
			ch->Append(_("GameGenie"));
		}
		else
		{
			ch->Append(_("Generic Code"));
			ch->Append(_("GameShark Advance"));
			ch->Append(_("CodeBreaker Advance"));
			ch->Append(_("Flashcart CHT"));
		}

		ch->SetSelection(0);
		return true;
	}
};

// manage the cheat search dialog
enum cf_vfmt
{
	CFVFMT_SD, CFVFMT_UD, CFVFMT_UH
};

// virtual ListCtrl for cheat search results
class CheatListCtrl : public wxListCtrl
{
public:
	wxArrayInt addrs;
	int cap_size; // size in effect when addrs were generated
	int count8, count16, count32; // number of aligned addresses in addrs
	wxString OnGetItemText(long item, long column) const;

	DECLARE_DYNAMIC_CLASS()
};

IMPLEMENT_DYNAMIC_CLASS(CheatListCtrl, wxListCtrl);

static class CheatFind_t : public wxEvtHandler
{
public:
	wxDialog* dlg;
	int valsrc, size, op, fmt;
	int ofmt, osize;
	wxString val_s;
	wxTextCtrl* val_tc;
	CheatListCtrl* list;

	// for enable/disable
	wxRadioButton* old_rb, *val_rb;
	wxControl* update_b, *clear_b, *add_b;

	bool isgb;

	// add dialog
	wxString ca_desc, ca_val;
	wxTextCtrl* ca_val_tc;
	wxControl* ca_fmt, *ca_addr;

	CheatFind_t() : wxEvtHandler(), valsrc(0), size(0), op(0), fmt(0), val_s() {}
	~CheatFind_t()
	{
		// not that it matters to anyone but mem leak detectors..
		cheatSearchCleanup(&cheatSearchData);
	}

	void Search(wxCommandEvent &ev)
	{
		dlg->TransferDataFromWindow();

		if (!valsrc && val_s.empty())
		{
			wxLogError(_("Number cannot be empty"));
			return;
		}

		if (!cheatSearchData.count)
			ResetSearch(ev);

		if (valsrc)
			cheatSearch(&cheatSearchData, op, size, fmt == CFVFMT_SD);
		else
			cheatSearchValue(&cheatSearchData, op, size, fmt == CFVFMT_SD,
			                 SignedValue());

		Deselect();
		list->addrs.clear();
		list->count8 = list->count16 = list->count32 = 0;
		list->cap_size = size;

		for (int i = 0; i < cheatSearchData.count; i++)
		{
			CheatSearchBlock* block = &cheatSearchData.blocks[i];

			for (int j = 0; j < block->size; j += (1 << size))
			{
				if (IS_BIT_SET(block->bits, j))
				{
					list->addrs.push_back((i << 28) + j);

					if (!(j & 1))
						list->count16++;

					if (!(j & 3))
						list->count32++;

					// since listctrl is virtual, it should be able to handle
					// at least 256k results, which is about the most you
					// will ever get
#if 0

					if (list->addrs.size() > 1000)
					{
						wxLogError(_("Search produced %d results.  Please refine better"),
						           list->addrs.size());
						list->addrs.clear();
						return;
					}

#endif
				}
			}
		}

		if (list->addrs.empty())
		{
			wxLogError(_("Search produced no results"));
			// no point in keeping empty search results around
			ResetSearch(ev);

			if (old_rb->GetValue())
			{
				val_rb->SetValue(true);
				// SetValue doesn't generate an event
				val_tc->Enable();
			}

			old_rb->Disable();
			update_b->Disable();
			clear_b->Disable();
		}
		else
		{
			switch (size)
			{
			case BITS_32:
				list->count16 = list->count32 * 2;

			// fall through
			case BITS_16:
				list->count8 = list->count16 * 2;
				break;

			case BITS_8:
				list->count8 = list->addrs.size();
			}

			old_rb->Enable();
			update_b->Enable();
			clear_b->Enable();
		}

		list->SetItemCount(list->addrs.size());
		list->Refresh();
	}

	void UpdateVals(wxCommandEvent &ev)
	{
		if (cheatSearchData.count)
		{
			cheatSearchUpdateValues(&cheatSearchData);

			if (list->count8)
				list->Refresh();

			update_b->Disable();
		}
	}

	void ResetSearch(wxCommandEvent &ev)
	{
		if (!cheatSearchData.count)
		{
			CheatSearchBlock* block = cheatSearchData.blocks;

			if (isgb)
			{
				block->offset = 0xa000;

				if (gbRam)
					block->data = gbRam;
				else
					block->data = &gbMemory[0xa000];

				block->saved = (u8*)malloc(gbRamSize);
				block->size = gbRamSize;
				block->bits = (u8*)malloc(gbRamSize >> 3);

				if (gbCgbMode)
				{
					block++;
					block->offset = 0xc000;
					block->data = &gbMemory[0xc000];
					block->saved = (u8*)malloc(0x1000);
					block->size = 0x1000;
					block->bits = (u8*)malloc(0x1000 >> 3);
					block++;
					block->offset = 0xd000;
					block->data = gbWram;
					block->saved = (u8*)malloc(0x8000);
					block->size = 0x8000;
					block->bits = (u8*)malloc(0x8000 >> 3);
				}
				else
				{
					block++;
					block->offset = 0xc000;
					block->data = &gbMemory[0xc000];
					block->saved = (u8*)malloc(0x2000);
					block->size = 0x2000;
					block->bits = (u8*)malloc(0x2000 >> 3);
				}
			}
			else
			{
				block->size = 0x40000;
				block->offset = 0x2000000;
				block->bits = (u8*)malloc(0x40000 >> 3);
				block->data = workRAM;
				block->saved = (u8*)malloc(0x40000);
				block++;
				block->size = 0x8000;
				block->offset = 0x3000000;
				block->bits = (u8*)malloc(0x8000 >> 3);
				block->data = internalRAM;
				block->saved = (u8*)malloc(0x8000);
			}

			cheatSearchData.count = (int)((block + 1) - cheatSearchData.blocks);
		}

		cheatSearchStart(&cheatSearchData);

		if (list->count8)
		{
			Deselect();
			list->count8 = list->count16 = list->count32 = 0;
			list->addrs.clear();
			list->SetItemCount(0);
			list->Refresh();

			if (old_rb->GetValue())
			{
				val_rb->SetValue(true);
				// SetValue doesn't generate an event
				val_tc->Enable();
			}

			old_rb->Disable();
			update_b->Disable();
			clear_b->Disable();
		}
	}

	void Deselect()
	{
		int idx = list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);

		if (idx >= 0)
			list->SetItemState(idx, 0, wxLIST_STATE_SELECTED);

		add_b->Disable();
	}

	void Select(wxListEvent &ev)
	{
		add_b->Enable(list->GetItemState(ev.GetIndex(), wxLIST_STATE_SELECTED) != 0);
	}

	void AddCheatB(wxCommandEvent &ev)
	{
		int idx = list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);

		if (idx >= 0)
			AddCheat(idx);
	}

	void AddCheatL(wxListEvent &ev)
	{
		AddCheat(ev.GetIndex());
	}

	void AddCheat(int idx)
	{
		wxString addr_s = list->OnGetItemText(idx, 0);
		ca_addr->SetLabel(addr_s);
		wxString s;

		switch (size)
		{
		case BITS_8:
			s = _("8-bit ");
			break;

		case BITS_16:
			s = _("16-bit ");
			break;

		case BITS_32:
			s = _("32-bit ");
			break;
		}

		switch (fmt)
		{
		case CFVFMT_SD:
			s += _("signed decimal");
			break;

		case CFVFMT_UD:
			s += _("unsigned decimal");
			break;

		case CFVFMT_UH:
			s += _("unsigned hexadecimal");
			break;
		}

		ca_fmt->SetLabel(s);
		// probably pointless (but inoffensive) to suggest a value
		ca_val = list->OnGetItemText(idx, 2); // sugest "New" value
		SetValVal(ca_val_tc);
		wxDialog* subdlg = GetXRCDialog("CheatAdd");
		dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);

		if (gopts.keep_on_top)
			subdlg->SetWindowStyle(subdlg->GetWindowStyle() | wxSTAY_ON_TOP);
		else
			subdlg->SetWindowStyle(subdlg->GetWindowStyle() & ~wxSTAY_ON_TOP);

		if (subdlg->ShowModal() != wxID_OK)
			return;

		if (ca_val.empty())
		{
			wxLogError(_("Number cannot be empty"));
			return;
		}

		u32 val = GetValue(ca_val, fmt);

		if (isgb)
		{
			long bank, addr;
			addr_s.ToLong(&bank, 16);
			addr_s.erase(0, 3);
			addr_s.ToLong(&addr, 16);

			if (addr >= 0xd000)
				bank += 0x90;
			else
				bank = 1;

			for (int i = 0; i < (1 << size); i++)
			{
				addr_s.Printf(wxT("%02X%02X%02X%02X"), bank, val & 0xff,
				              addr & 0xff, addr >> 8);
				gbAddGsCheat(addr_s.mb_str(), ca_desc.mb_str());
				val >>= 8;
				addr++;
			}
		}
		else
		{
			wxString s;

			switch (size)
			{
			case BITS_8:
				s.Printf(wxT(":%02X"), val);
				break;

			case BITS_16:
				s.Printf(wxT(":%04X"), val);
				break;

			case BITS_32:
				s.Printf(wxT(":%08X"), val);
				break;
			}

			addr_s.append(s);
			cheatsAddCheatCode(addr_s.mb_str(), ca_desc.mb_str());
		}
	}

	void SetValVal(wxTextCtrl* tc)
	{
		wxTextValidator* v = wxStaticCast(tc->GetValidator(), wxTextValidator);

		switch (fmt)
		{
		case CFVFMT_SD:
			v->SetIncludes(val_sigdigits);
			break;

		case CFVFMT_UD:
			v->SetIncludes(val_unsdigits);
			break;

		case CFVFMT_UH:
			v->SetIncludes(val_hexdigits);
			break;
		}
	}

	u32 GetValue(wxString &s, int fmt)
	{
		long val;
		// FIXME: probably ought to throw an error if ToLong
		// returns false or val is out of range
		s.ToLong(&val, fmt == CFVFMT_UH ? 16 : 10);

		if (size != BITS_32)
			val &= size == BITS_8 ? 0xff : 0xffff;

		return val;
	}

	u32 GetValue(int fmt)
	{
		return GetValue(val_s, fmt);
	}

	u32 GetValue()
	{
		return GetValue(fmt);
	}

	s32 SignedValue(wxString &s, int fmt)
	{
		s32 val = GetValue(s, fmt);

		if (fmt == CFVFMT_SD)
		{
			if (size == BITS_8)
				val = (s32)(s8)val;
			else if (size == BITS_16)
				val = (s32)(s16)val;
		}

		return val;
	}

	s32 SignedValue(int fmt)
	{
		return SignedValue(val_s, fmt);
	}

	s32 SignedValue()
	{
		return SignedValue(fmt);
	}

	void FormatValue(s32 val, wxString &s)
	{
		if (fmt != CFVFMT_SD && size != BITS_32)
			val &= size == BITS_8 ? 0xff : 0xffff;

		switch (fmt)
		{
		case CFVFMT_SD:
			s.Printf(wxT("%d"), val);
			break;

		case CFVFMT_UD:
			s.Printf(wxT("%u"), val);
			break;

		case CFVFMT_UH:
			switch (size)
			{
			case BITS_8:
				s.Printf(wxT("%02X"), val);
				break;

			case BITS_16:
				s.Printf(wxT("%04X"), val);
				break;

			case BITS_32:
				s.Printf(wxT("%08X"), val);
				break;
			}
		}
	}

	void UpdateView(wxCommandEvent &ev)
	{
		dlg->TransferDataFromWindow();

		if (ofmt != fmt && !val_s.empty())
		{
			s32 val = GetValue(ofmt);

			switch (fmt)
			{
			case CFVFMT_SD:
				switch (size)
				{
				case BITS_8:
					val = (s32)(s8)val;
					break;

				case BITS_16:
					val = (s32)(s16)val;
				}

				val_s.Printf(wxT("%d"), val);
				break;

			case CFVFMT_UD:
				val_s.Printf(wxT("%u"), val);
				break;

			case CFVFMT_UH:
				val_s.Printf(wxT("%x"), val);
				break;
			}

			val_tc->SetValue(val_s);
		}

		if (ofmt != fmt)
			SetValVal(val_tc);

		if (list->count8 && osize != size)
		{
			switch (size)
			{
			case BITS_32:
				list->SetItemCount(list->count32);
				break;

			case BITS_16:
				list->SetItemCount(list->count16);
				break;

			case BITS_8:
				list->SetItemCount(list->count8);
				break;
			}
		}

		if (ofmt != fmt || osize != size)
			list->Refresh();

		ofmt = fmt;
		osize = size;
	}

	void EnableVal(wxCommandEvent &ev)
	{
		val_tc->Enable(ev.GetId() == XRCID("SpecificValue"));
	}

} cheat_find_handler;

// clear cheat find dialog between games
void MainFrame::ResetCheatSearch()
{
	CheatFind_t &cfh = cheat_find_handler;
	cfh.fmt = cfh.size = cfh.op = cfh.valsrc = 0;
	cfh.val_s = wxEmptyString;
	cfh.Deselect();
	cfh.list->SetItemCount(0);
	cfh.list->count8 = cfh.list->count16 = cfh.list->count32 = 0;
	cfh.list->addrs.clear();
	cfh.ca_desc = wxEmptyString;
	cheatSearchCleanup(&cheatSearchData);
}

// onshow handler for above, in the form of an overzealous validator
class CheatFindFill : public wxValidator
{
public:
	CheatFindFill() : wxValidator() {}
	CheatFindFill(const CheatFindFill &e) : wxValidator() {}
	wxObject* Clone() const { return new CheatFindFill(*this); }
	bool TransferFromWindow() { return true; }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		CheatFind_t &cfh = cheat_find_handler;
		GameArea* panel = wxGetApp().frame->GetPanel();
		cfh.isgb = panel->game_type() == IMAGE_GB;
		cfh.val_tc->Enable(!cfh.valsrc);
		cfh.ofmt = cfh.fmt;
		cfh.SetValVal(cfh.val_tc);
		return true;
	}
};

// the implementation of the virtual list ctrl for search results
// requires CheatFind_t to be implemented
wxString CheatListCtrl::OnGetItemText(long item, long column) const
{
	wxString s;
	CheatFind_t &cfh = cheat_find_handler;
	// allowing GUI to change format after search makes this a little
	// more complicated than necessary...
	int off = 0;
	int size = cfh.size;

	if (cap_size > size)
	{
		off = (item & ((1 << (cap_size - size)) - 1)) << size;
		item >>= cap_size - size;
	}
	else if (cap_size < size)
	{
		for (int i = 0; i < addrs.size(); i++)
		{
			if (!(addrs[i] & ((1 << size) - 1)) && !item--)
			{
				item = i;
				break;
			}
		}
	}

	CheatSearchBlock* block = &cheatSearchData.blocks[addrs[item] >> 28];
	off += addrs[item] & 0xfffffff;

	switch (column)
	{
	case 0: // address
		if (cfh.isgb)
		{
			int bank = 0;
			int addr = block->offset;

			if (block->offset == 0xa000)
			{
				bank = off / 0x2000;
				addr += off % 0x2000;
			}
			else if (block->offset == 0xd000)
			{
				bank = off / 0x1000;
				addr += off % 0x1000;
			}
			else
				addr += off;

			s.Printf(wxT("%02X:%04X"), bank, addr);
		}
		else
			s.Printf(wxT("%08X"), block->offset + off);

		break;

	case 1: // old
		cfh.FormatValue(cheatSearchSignedRead(block->saved, off, size), s);
		break;

	case 2: // new
		cfh.FormatValue(cheatSearchSignedRead(block->data, off, size), s);
		break;
	}

	return s;
}

// these are the choices for canned colors; their order must match the
// names in the choice control
static const u16 defaultPalettes[][8] =
{
	{
		// Standard
		0x7FFF, 0x56B5, 0x318C, 0x0000,  0x7FFF, 0x56B5, 0x318C, 0x0000,
	},
	{
		// Blue Sea
		0x6200, 0x7E10, 0x7C10, 0x5000,  0x6200, 0x7E10, 0x7C10, 0x5000,
	},
	{
		// Dark Night
		0x4008, 0x4000, 0x2000, 0x2008,  0x4008, 0x4000, 0x2000, 0x2008,
	},
	{
		// Green Forest
		0x43F0, 0x03E0, 0x4200, 0x2200,  0x43F0, 0x03E0, 0x4200, 0x2200,
	},
	{
		// Hot Desert
		0x43FF, 0x03FF, 0x221F, 0x021F,  0x43FF, 0x03FF, 0x221F, 0x021F,
	},
	{
		// Pink Dreams
		0x621F, 0x7E1F, 0x7C1F, 0x2010,  0x621F, 0x7E1F, 0x7C1F, 0x2010,
	},
	{
		// Weird Colors
		0x621F, 0x401F, 0x001F, 0x2010,  0x621F, 0x401F, 0x001F, 0x2010,
	},
	{
		// Real GB Colors
		0x1B8E, 0x02C0, 0x0DA0, 0x1140,  0x1B8E, 0x02C0, 0x0DA0, 0x1140,
	},
	{
		// Real 'GB on GBASP' Colors
		0x7BDE, /*0x23F0*/ 0x5778, /*0x5DC0*/ 0x5640, 0x0000,  0x7BDE, /*0x3678*/ 0x529C, /*0x0980*/ 0x2990, 0x0000,
	}
};

// manage the GB color prefs' canned color selecter
static class GBColorConfig_t : public wxEvtHandler
{
public:
	wxWindow* p;
	wxChoice* c;
	wxColourPickerCtrl* cp[8];
	int pno;
	void ColorSel(wxCommandEvent &ev)
	{
		if (ev.GetSelection() > 0)
		{
			const u16* color = defaultPalettes[ev.GetSelection() - 1];

			for (int i = 0; i < 8; i++, color++)
				cp[i]->SetColour(wxColor(((*color << 3) & 0xf8),
				                         ((*color >> 2) & 0xf8),
				                         ((*color >> 7) & 0xf8)));
		}
	}
	void ColorReset(wxCommandEvent &ev)
	{
		const u16* color = &systemGbPalette[pno * 8];

		for (int i = 0; i < 8; i++, color++)
			cp[i]->SetColour(wxColor(((*color << 3) & 0xf8),
			                         ((*color >> 2) & 0xf8),
			                         ((*color >> 7) & 0xf8)));
	}

	void ColorButton(wxCommandEvent &ev)
	{
		c->SetSelection(0);
	}
} GBColorConfigHandler[3];

// disable controls if a GBA game is not loaded
class GBACtrlEnabler : public wxValidator
{
public:
	GBACtrlEnabler() : wxValidator() {}
	GBACtrlEnabler(const GBACtrlEnabler &e) : wxValidator() {}
	wxObject* Clone() const { return new GBACtrlEnabler(*this); }
	bool TransferFromWindow() { return true; }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		GetWindow()->Enable(wxGetApp().frame->GetPanel()->game_type() == IMAGE_GBA);
		return true;
	}
};

// manage save game area settings for GBA prefs
static class BatConfig_t : public wxEvtHandler
{
public:
	wxChoice* type, *size;
	void ChangeType(wxCommandEvent &ev)
	{
		int i = ev.GetSelection();
		size->Enable(!i || i == 3); // automatic/flash
	}
	void Detect(wxCommandEvent &ev)
	{
		u32 sz = wxGetApp().frame->GetPanel()->game_size();
		utilGBAFindSave(sz);
		type->SetSelection(saveType);

		if (saveType == 3)
		{
			size->SetSelection(flashSize == 0x20000 ? 1 : 0);
			size->Enable();
		}
		else
		{
			size->Disable();
		}
	}
} BatConfigHandler;

// manage the sound prefs dialog
static class SoundConfig_t : public wxEvtHandler
{
public:
	wxSlider* vol, *bufs;
	wxControl* bufinfo;
	int lastapi;
	wxChoice* dev;
	wxControl* umix, *hwacc;
	wxArrayString dev_ids;

	void FullVol(wxCommandEvent &ev)
	{
		vol->SetValue(100);
	}
	void AdjustFrames(int count)
	{
		wxString s;
		s.Printf(_("%d frames = %.2f ms"), count, (double)count / 60.0 * 1000.0);
		bufinfo->SetLabel(s);
	}
	void AdjustFramesEv(wxCommandEvent &ev)
	{
		AdjustFrames(bufs->GetValue());
	}

	bool FillDev(int api)
	{
		dev->Clear();
		dev->Append(_("Default device"));
		dev_ids.clear();
		wxArrayString names;

		switch (api)
		{
		case AUD_SDL:
			break;
#ifndef NO_OAL

		case AUD_OPENAL:
			if (!GetOALDevices(names, dev_ids))
				return false;

			break;
#endif
#ifdef __WXMSW__

		case AUD_DIRECTSOUND:
			if (!(GetDSDevices(names, dev_ids)))
				return false;

			break;
#ifndef NO_XAUDIO2

		case AUD_XAUDIO2:
			if (!GetXA2Devices(names, dev_ids))
				return false;

			break;
#endif
#endif
		}

		dev->SetSelection(0);

		for (int i = 0; i < names.size(); i++)
		{
			dev->Append(names[i]);

			if (api == gopts.audio_api && gopts.audio_dev == dev_ids[i])
				dev->SetSelection(i + 1);
		}

		umix->Enable(api == AUD_XAUDIO2);
		hwacc->Enable(api == AUD_DIRECTSOUND);
		lastapi = api;
		return true;
	}
	void SetAPI(wxCommandEvent &ev)
	{
		int api = gopts.audio_api;
		wxValidator* v = wxStaticCast(ev.GetEventObject(), wxWindow)->GetValidator();
		v->TransferFromWindow();
		int newapi = gopts.audio_api;
		gopts.audio_api = api;

		if (newapi == lastapi)
			return;

		FillDev(newapi);
	}
} sound_config_handler;

// Validator/widget filler for sound device selector & time indicator
class SoundConfigLoad : public wxValidator
{
public:
	SoundConfigLoad() : wxValidator() {}
	SoundConfigLoad(const SoundConfigLoad &e) : wxValidator() {}
	wxObject* Clone() const { return new SoundConfigLoad(*this); }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		SoundConfig_t &sch = sound_config_handler;
		sch.FillDev(gopts.audio_api);
		sch.AdjustFrames(gopts.audio_buffers);
		return true;
	}
	bool TransferFromWindow()
	{
		SoundConfig_t &sch = sound_config_handler;
		int devs = sch.dev->GetSelection();

		if (devs <= 0)
			gopts.audio_dev = wxEmptyString;
		else
			gopts.audio_dev = sch.dev_ids[devs - 1];

		return true;
	}
};

// manage the joypad prefs' per-panel default/clear buttons
static class JoyPadConfig_t : public wxEvtHandler
{
public:
	wxWindow* p;
	void JoypadConfigButtons(wxCommandEvent &ev)
	{
		bool clear = ev.GetId() == XRCID("Clear");

		for (int i = 0; i < NUM_KEYS; i++)
		{
			wxJoyKeyTextCtrl* tc = XRCCTRL_D(*p, joynames[i], wxJoyKeyTextCtrl);

			if (clear)
				tc->SetValue(wxEmptyString);
			else
			{
				wxJoyKeyBinding_v a;

				if (defkeys[i * 2].key)
					a.push_back(defkeys[i * 2]);

				if (defkeys[i * 2 + 1].joy)
					a.push_back(defkeys[i * 2 + 1]);

				tc->SetValue(wxJoyKeyTextCtrl::ToString(a));
			}
		}
	}
} JoyPadConfigHandler[4];

// manage fullscreen mode widget
// technically, it's more than a validator: it modifies the widget as well
class ScreenModeList : public wxValidator
{
public:
	ScreenModeList() : wxValidator() {}
	ScreenModeList(const ScreenModeList &e) : wxValidator() {}
	wxObject* Clone() const { return new ScreenModeList(*this); }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		wxChoice* c = wxStaticCast(GetWindow(), wxChoice);
		wxDisplay d(wxDisplay::GetFromWindow(c->GetParent()));
		c->Clear();
		int modeno = 0, bestmode = 0;
		int bm_bpp = 0;
		c->Append(_("Desktop mode"));
		// probably ought to just disable this whole control on UNIX/X11 since
		// wxDisplay is so broken.
		vm = d.GetModes();
		wxString s;

		for (int i = 0; i < vm.size(); i++)
		{
			s.Printf(_("%d x %d - %dbpp @ %dHz"), vm[i].w, vm[i].h, vm[i].bpp, vm[i].refresh);
			c->Append(s);

			if (!modeno && gopts.fs_mode.w == vm[i].w && gopts.fs_mode.h == vm[i].h)
			{
				if (gopts.fs_mode.bpp == vm[i].bpp && gopts.fs_mode.refresh == vm[i].refresh)
					modeno = i + 1;
				else if (vm[i].bpp == gopts.fs_mode.bpp &&
				         bm_bpp != gopts.fs_mode.bpp)
				{
					bestmode = i + 1;
					bm_bpp = vm[i].bpp;
				}
				else if (bm_bpp != gopts.fs_mode.bpp && bm_bpp != 32 &&
				         vm[i].bpp == 32)
				{
					bm_bpp = vm[i].bpp;
					bestmode = i + 1;
				}
				else if (bm_bpp != gopts.fs_mode.bpp && bm_bpp < 24 &&
				         vm[i].bpp == 24)
				{
					bm_bpp = vm[i].bpp;
					bestmode = i + 1;
				}
				else if (bm_bpp != gopts.fs_mode.bpp && bm_bpp < 24 &&
				         bm_bpp != 16 && vm[i].bpp == 16)
				{
					bm_bpp = vm[i].bpp;
					bestmode = i + 1;
				}
				else if (!bm_bpp)
				{
					bm_bpp = vm[i].bpp;
					bestmode = i + 1;
				}
			}
		}

		if (!modeno && bestmode)
			modeno = bestmode;

		c->SetSelection(modeno);
		return true;
	}
	bool TransferFromWindow()
	{
		int bestmode = wxStaticCast(GetWindow(), wxChoice)->GetSelection();

		if (!bestmode)
			gopts.fs_mode.h = gopts.fs_mode.w = gopts.fs_mode.bpp = gopts.fs_mode.refresh = 0;
		else
			gopts.fs_mode = vm[bestmode - 1];

		return true;
	}
private:
	wxArrayVideoModes vm;
};

// enable plugin-related iff filter choice is plugin
class PluginEnabler : public wxValidator
{
public:
	PluginEnabler() : wxValidator() {}
	PluginEnabler(const PluginEnabler &e) : wxValidator() {}
	wxObject* Clone() const { return new PluginEnabler(*this); }
	bool TransferFromWindow() { return true; }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		GetWindow()->Enable(gopts.filter == FF_PLUGIN);
		return true;
	}
};

// The same, but as an event handler
static class PluginEnable_t : public wxEvtHandler
{
public:
	wxWindow* lab, *ch;
	void ToggleChoice(wxCommandEvent &ev)
	{
		bool en = ev.GetSelection() == FF_PLUGIN;
		lab->Enable(en);
		ch->Enable(en);
	}
} PluginEnableHandler;

// fill in plugin list
class PluginListFiller : public PluginEnabler
{
public:
	PluginListFiller(wxDialog* parent, wxControl* lab, wxChoice* ch) :
		PluginEnabler(), txt(lab), dlg(parent), plugins(), filtch(ch) {}
	PluginListFiller(const PluginListFiller &e) :
		PluginEnabler(), txt(e.txt), dlg(e.dlg), plugins(e.plugins),
		filtch(e.filtch) {}
	wxObject* Clone() const { return new PluginListFiller(*this); }
	bool Validate(wxWindow* p) { return true; }
	bool TransferToWindow()
	{
		PluginEnabler::TransferToWindow();
		wxChoice* ch = wxStaticCast(GetWindow(), wxChoice);
		ch->Clear();
		ch->Append(_("None"));
		plugins.clear();
		const wxString &plpath = wxStandardPaths::Get().GetPluginsDir();
		wxDir::GetAllFiles(plpath, &plugins, wxT("*.rpi"));

		for (int i = 0; i < plugins.size(); i++)
		{
			wxDynamicLibrary dl(plugins[i], wxDL_VERBATIM | wxDL_NOW);
			RENDPLUG_GetInfo GetInfo;
			const RENDER_PLUGIN_INFO* rpi;

			if (dl.IsLoaded() &&
			        (GetInfo = (RENDPLUG_GetInfo)dl.GetSymbol(wxT("RenderPluginGetInfo"))) &&
			        // note that in actual kega fusion plugins, rpi->Output is
			        // unused (as is rpi->Handle)
			        dl.GetSymbol(wxT("RenderPluginOutput")) &&
			        (rpi = GetInfo()) &&
			        // FIXME: maybe this should be >= RPI_VERISON
			        (rpi->Flags & 0xff) == RPI_VERSION &&
			        // RPI_565_SUPP is not supported
			        // although it would be possible
			        // and it would make Cairo more efficient
			        (rpi->Flags & (RPI_555_SUPP | RPI_888_SUPP)))
			{
				wxFileName fn(plugins[i]);
				wxString s = fn.GetName();
				s += wxT(": ");
				s += wxString(rpi->Name, wxConvUTF8, sizeof(rpi->Name));
				fn.MakeRelativeTo(plpath);
				plugins[i] = fn.GetFullName();
				ch->Append(s);

				if (plugins[i] == gopts.filter_plugin)
					ch->SetSelection(i + 1);
			}
			else
				plugins.RemoveAt(i--);
		}

		if (ch->GetCount() == 1)
		{
			// this is probably the only place the user can find out where
			// to put the plugins...  it depends on where program was
			// installed, and of course OS
			wxString msg;
			msg.Printf(_("No usable rpi plugins found in %s"), plpath.c_str());
			systemScreenMessage(msg);
			ch->Hide();
			txt->Hide();
			int cursel = filtch->GetSelection();

			if (cursel == FF_PLUGIN)
				cursel = 0;

			if (filtch->GetCount() == FF_PLUGIN + 1)
			{
				filtch->Delete(FF_PLUGIN);
				// apparently wxgtk loses selection after this, even
				// if selection was not FF_PLUGIN
				filtch->SetSelection(cursel);
			}
		}
		else
		{
			ch->Show();
			txt->Show();

			if (filtch->GetCount() < FF_PLUGIN + 1)
				filtch->Append(_("Plugin"));
		}

		// FIXME: this isn't enough.  It only resizes 2nd time around
		dlg->Fit();
		return true;
	}
	bool TransferFromWindow()
	{
		wxChoice* ch = wxStaticCast(GetWindow(), wxChoice);

		if (ch->GetCount() == 1)
		{
			gopts.filter_plugin = wxEmptyString;

			// this happens if "Plugin" was selected and the entry was
			// subsequently removed
			if (ch->GetSelection() < 0)
				ch->SetSelection(0);

			if (gopts.filter < 0)
				gopts.filter = 0;
		}
		else
		{
			int n = ch->GetSelection();

			if (n > 0)
				gopts.filter_plugin = plugins[n - 1];
			else
			{
				if (filtch->GetSelection() == FF_PLUGIN)
				{
					wxMessageBox(_("Please select a plugin or a different filter"),
					             _("Plugin selection error"), wxOK | wxICON_ERROR);
					return false;
				}

				gopts.filter_plugin = wxEmptyString;
			}
		}

		return true;
	}
private:
	wxDialog* dlg;
	wxControl* txt;
	wxChoice* filtch;
	wxArrayString plugins;
};


// this is the cmd table index for the accel tree ctrl
// one of the "benefits" of using TreeItemData is that we have to
// malloc them all, because treectrl destructor will free them all
// that means we can't use e.g. a single static table of len ncmds
class TreeInt : public wxTreeItemData
{
public:
	TreeInt(int i) : wxTreeItemData() { val = i; }
	int val;
};

// Convert a tree selection ID to a name
// root
//   parent
//     item
static bool treeid_to_name(int id, wxString &name, wxTreeCtrl* tc,
                           const wxTreeItemId &parent, int lev = 0)
{
	wxTreeItemIdValue cookie;

	for (wxTreeItemId tid = tc->GetFirstChild(parent, cookie); tid.IsOk();
	        tid = tc->GetNextChild(parent, cookie))
	{
		const TreeInt* ti = static_cast<const TreeInt*>(tc->GetItemData(tid));

		if (ti && ti->val == id)
		{
			name = wxString(wxT(' '), 2 * lev) + tc->GetItemText(tid);
			return true;
		}

		if (treeid_to_name(id, name, tc, tid, lev + 1))
		{
			name = wxString(wxT(' '), 2 * lev) + tc->GetItemText(tid) + wxT('\n') + name;
			return true;
		}
	}

	return false;
}

// for sorting accels by command ID
static bool cmdid_lt(const wxAcceleratorEntry &a, const wxAcceleratorEntry &b)
{
	return a.GetCommand() < b.GetCommand();
}

// manage the accel editor dialog
static class AccelConfig_t : public wxEvtHandler
{
public:
	wxTreeCtrl* tc;
	wxControlWithItems* lb;
	wxAcceleratorEntry_v user_accels, accels;
	wxWindow* asb, *remb;
	wxKeyTextCtrl* key;
	wxControl* curas;

	// since this is not the actual dialog, derived from wxDialog, which is
	// the normal way of doing things, do init on the show event instead of
	// constructor
	void Init(wxShowEvent &ev)
	{
#if wxCHECK_VERSION(2,9,0)
#define GetShow IsShown
#endif
		ev.Skip();

		if (!ev.GetShow())
			return;

		lb->Clear();
		tc->Unselect();
		tc->ExpandAll();
		user_accels = gopts.accels;
		key->SetValue(wxT(""));
		asb->Enable(false);
		remb->Enable(false);
		curas->SetLabel(wxT(""));
		accels = wxGetApp().frame->get_accels(user_accels);
	}

	// on OK, save the accels in gopts
	void Set(wxCommandEvent &ev)
	{
		// opts.cpp assumes that gopts.accels entries with same command ID
		// are contiguous, so sort first
		std::sort(gopts.accels.begin(), gopts.accels.end(), cmdid_lt);
		gopts.accels = user_accels;
		wxGetApp().frame->set_global_accels();
		ev.Skip();
	}

	// After selecting item in command list, fill in key list
	// and maybe enable asb
	void CommandSel(wxTreeEvent &ev)
	{
		// wxTreeCtrl *tc = wxStaticCast(evt.GetEventObject(), wxTreeCtrl);
		// can't use wxStaticCast; wxTreeItemData does not derive from wxObject
		const TreeInt* id = static_cast<const TreeInt*>(tc->GetItemData(ev.GetItem()));

		if (!id)
		{
			ev.Veto();
			return;
		}

		if (ev.GetEventType() == wxEVT_COMMAND_TREE_SEL_CHANGING)
		{
			ev.Skip();
			return;
		}

		lb->Clear();
		remb->Enable(false);
		asb->Enable(!key->GetValue().empty());
		int cmd = id->val;

		for (int i = 0; i < accels.size(); i++)
			if (accels[i].GetCommand() == cmdtab[cmd].cmd_id)
				lb->Append(wxKeyTextCtrl::ToString(accels[i].GetFlags(),
				                                   accels[i].GetKeyCode()));
	}

	// after selecting a key in key list, enable Remove button
	void KeySel(wxCommandEvent &ev)
	{
		remb->Enable(lb->GetSelection() != wxNOT_FOUND);
	}

	// remove selected binding
	void Remove(wxCommandEvent &ev)
	{
		int lsel = lb->GetSelection();

		if (lsel == wxNOT_FOUND)
			return;

		wxString selstr = lb->GetString(lsel);
		int selmod, selkey;

		if (!wxKeyTextCtrl::FromString(selstr, selmod, selkey))
			return; // this should never happen

		remb->Enable(false);

		// if this key is currently in the shortcut field, clear out curas
		if (selstr == key->GetValue())
			curas->SetLabel(wxT(""));

		lb->Delete(lsel);

		// first drop from user accels, if applicable
		for (wxAcceleratorEntry_v::iterator i = user_accels.begin();
		        i < user_accels.end(); i++)
			if (i->GetFlags() == selmod && i->GetKeyCode() == selkey)
			{
				user_accels.erase(i);
				break;
			}

		// if it's a system accel, disable by assigning to NOOP
		wxAcceleratorEntry_v &sys_accels = wxGetApp().frame->sys_accels;

		for (int i = 0; i < sys_accels.size(); i++)
			if (sys_accels[i].GetFlags() == selmod &&
			        sys_accels[i].GetKeyCode() == selkey)
			{
				wxAcceleratorEntry ne(selmod, selkey, XRCID("NOOP"));
				user_accels.push_back(ne);
			}

		// finally, remove from accels instead of recomputing
		for (wxAcceleratorEntry_v::iterator i = accels.begin();
		        i < accels.end(); i++)
			if (i->GetFlags() == selmod && i->GetKeyCode() == selkey)
			{
				accels.erase(i);
				break;
			}
	}

	// wipe out all user bindings
	void ResetAll(wxCommandEvent &ev)
	{
		if (user_accels.empty() ||
		        wxMessageBox(_("This will clear all user-defined accelerators.  Are you sure?"),
		                     _("Confirm"), wxYES_NO) != wxYES)
			return;

		user_accels.clear();
		accels = wxGetApp().frame->sys_accels;
		tc->Unselect();
		lb->Clear();
		// rather than recomputing curas, just clear it
		key->SetValue(wxT(""));
		curas->SetLabel(wxT(""));
	}

	// remove old key binding, add new key binding, and update GUI
	void Assign(wxCommandEvent &ev)
	{
		wxTreeItemId csel = tc->GetSelection();
		wxString accel = key->GetValue();

		if (!csel.IsOk() || accel.empty())
			return;

		int acmod, ackey;

		if (!wxKeyTextCtrl::FromString(accel, acmod, ackey))
			return; // this should never happen

		for (int i = 0; i < lb->GetCount(); i++)
			if (lb->GetString(i) == accel)
				return; // ignore attempts to add twice

		lb->Append(accel);

		// first drop from user accels, if applicable
		for (wxAcceleratorEntry_v::iterator i = user_accels.begin();
		        i < user_accels.end(); i++)
			if (i->GetFlags() == acmod && i->GetKeyCode() == ackey)
			{
				user_accels.erase(i);
				break;
			}

		// then assign to this command
		const TreeInt* id = static_cast<const TreeInt*>(tc->GetItemData(csel));
		wxAcceleratorEntry ne(acmod, ackey, cmdtab[id->val].cmd_id);
		user_accels.push_back(ne);
		// now assigned to this cmd...
		wxString lab;
		treeid_to_name(id->val, lab, tc, tc->GetRootItem());
		curas->SetLabel(lab);
		// finally, instead of recomputing accels, just append new accel
		accels.push_back(ne);
	}

	// update curas and maybe enable asb
	void CheckKey(wxCommandEvent &ev)
	{
		wxString nkey = key->GetValue();

		if (nkey.empty())
		{
			curas->SetLabel(wxT(""));
			asb->Enable(false);
			return;
		}

		int acmod, ackey;

		if (!wxKeyTextCtrl::FromString(nkey, acmod, ackey))
		{
			// this should never happen
			key->SetValue(wxT(""));
			asb->Enable(false);
			return;
		}

		asb->Enable(tc->GetSelection().IsOk());
		int cmd = -1;

		for (int i = 0; i < accels.size(); i++)
			if (accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey)
			{
				int cmdid = accels[i].GetCommand();

				for (cmd = 0; cmd < ncmds; cmd++)
					if (cmdid == cmdtab[cmd].cmd_id)
						break;

				break;
			}

		if (cmd < 0 || cmdtab[cmd].cmd_id == XRCID("NOOP"))
		{
			curas->SetLabel(wxT(""));
			return;
		}

		wxString lab;
		treeid_to_name(cmd, lab, tc, tc->GetRootItem());
		curas->SetLabel(lab);
	}
} accel_config_handler;

// build initial accel tree control from menu
void MainFrame::add_menu_accels(wxTreeCtrl* tc, wxTreeItemId &parent, wxMenu* menu)
{
	wxMenuItemList mil = menu->GetMenuItems();

	for (wxMenuItemList::iterator mi = mil.begin(); mi != mil.end(); mi++)
	{
		if ((*mi)->IsSeparator())
		{
			tc->AppendItem(parent, wxT("-----"));
		}
		else if ((*mi)->IsSubMenu())
		{
			wxTreeItemId id = tc->AppendItem(parent, (*mi)->GetItemLabelText());
			add_menu_accels(tc, id, (*mi)->GetSubMenu());

			if ((*mi)->GetSubMenu() == recent)
			{
				for (int i = wxID_FILE1; i <= wxID_FILE10; i++)
				{
					int cmdid;

					for (cmdid = 0; cmdid < ncmds; cmdid++)
						if (cmdtab[cmdid].cmd_id == i)
							break;

					TreeInt* val = new TreeInt(cmdid);
					tc->AppendItem(id, cmdtab[cmdid].name, -1, -1, val);
				}
			}
		}
		else
		{
			int mid = (*mi)->GetId();

			if (mid >= wxID_FILE1 && mid <= wxID_FILE10)
				continue;

			int cmdid;

			for (cmdid = 0; cmdid < ncmds; cmdid++)
				if (cmdtab[cmdid].cmd_id == mid)
					break;

			if (cmdid == ncmds)
				continue; // bad menu item; should inform user really

			TreeInt* val = new TreeInt(cmdid);
			// ugh.  There has to be a better way...
			// perhaps make XRCID ranges a requirement for load/save st?
			// but then if the user overides main menu, that req. is broken..
			wxString txt = (*mi)->GetItemLabelText();

			for (int i = 0; i < 10; i++)
				if (*mi == loadst_mi[i] || *mi == savest_mi[i])
				{
					txt = cmdtab[i].name;
					break;
				}

			tc->AppendItem(parent, txt, -1, -1, val);
		}
	}
}

// manage throttle spinctrl/canned setting choice interaction
static class ThrottleCtrl_t : public wxEvtHandler
{
public:
	wxSpinCtrl* thr;
	wxChoice* thrsel;

	// set thrsel from thr
	void SetThrottleSel(wxSpinEvent &evt)
	{
		DoSetThrottleSel(thr->GetValue());
	}

	void DoSetThrottleSel(int val)
	{
		switch (val)
		{
		case 0:
			thrsel->SetSelection(1);
			break;

		case 25:
			thrsel->SetSelection(2);
			break;

		case 50:
			thrsel->SetSelection(3);
			break;

		case 100:
			thrsel->SetSelection(4);
			break;

		case 150:
			thrsel->SetSelection(5);
			break;

		case 200:
			thrsel->SetSelection(6);
			break;

		default:
			thrsel->SetSelection(0);
			break;
		}
	}

	// set thr from thrsel
	void SetThrottle(wxCommandEvent &evt)
	{
		switch (thrsel->GetSelection())
		{
		case 0: // blank; leave it alone
			break;

		case 1:
			thr->SetValue(0);
			break;

		case 2:
			thr->SetValue(25);
			break;

		case 3:
			thr->SetValue(50);
			break;

		case 4:
			thr->SetValue(100);
			break;

		case 5:
			thr->SetValue(150);
			break;

		case 6:
			thr->SetValue(200);
			break;
		}
	}

	// since this is not the actual dialog, derived from wxDialog, which is
	// the normal way of doing things, do init on the show event instead of
	// constructor
	// could've also used a validator, I guess...
	void Init(wxShowEvent &ev)
	{
		ev.Skip();
		DoSetThrottleSel(throttle);
	}
} throttle_ctrl;

/////////////////////////////
//Check if a pointer from the XRC file is valid. If it's not, throw an error telling the user.
template <typename T>
void CheckThrowXRCError(T pointer, std::string name)
{
	if (pointer == NULL)
	{
		std::string errormessage = "Unable to load a \"";
		errormessage += typeid(pointer).name();
		errormessage += "\" from the builtin xrc file: ";
		errormessage += name;
		throw std::runtime_error(errormessage);
	}
}
wxDialog* MainFrame::LoadXRCDialog(const char* name)
{
	wxString dname = wxString::FromUTF8(name);
	wxDialog* dialog = wxXmlResource::Get()->LoadDialog(this, dname);
	CheckThrowXRCError(dialog, name);
	/* wx-2.9.1 doesn't set parent for propertysheetdialogs for some reason */
	/* this will generate a gtk warning but it is necessary for later */
	/* retrieval using FindWindow() */
#if (wxMAJOR_VERSION < 3)

	if (!dialog->GetParent())
		dialog->Reparent(this);

#endif
	mark_recursive(dialog);
	return dialog;
}

wxDialog* MainFrame::LoadXRCropertySheetDialog(const char* name)
{
	wxString dname = wxString::FromUTF8(name);
	//Seems like the only way to do this
	wxObject* anObject = wxXmlResource::Get()->LoadObject(this, dname, wxEmptyString);
	wxDialog* dialog = dynamic_cast<wxDialog*>(anObject);
	CheckThrowXRCError(dialog, name);
	/* wx-2.9.1 doesn't set parent for propertysheetdialogs for some reason */
	/* this will generate a gtk warning but it is necessary for later */
	/* retrieval using FindWindow() */
#if (wxMAJOR_VERSION < 3)

	if (!dialog->GetParent())
		dialog->Reparent(this);

#endif
	mark_recursive(dialog);
	return dialog;
}

//This just adds some error checking to the wx XRCCTRL macro
template <typename T>
T* SafeXRCCTRL(wxWindow* parent, const char* name)
{
	wxString dname = wxString::FromUTF8(name);
	//This is needed to work around a bug in XRCCTRL
	wxString Ldname = dname;
	T* output = XRCCTRL(*parent, dname, T);
	CheckThrowXRCError(output, name);
	return output;
}

///Get an object, and set the appropriate validator
//T is the object type, and V is the validator type
template <typename T, typename V>
T* GetValidatedChild(wxWindow* parent, const char* name, V validator)
{
	T* child = SafeXRCCTRL<T>(parent, name);
	child->SetValidator(validator);
	return child;
}

wxAcceleratorEntry_v MainFrame::get_accels(wxAcceleratorEntry_v user_accels)
{
	// set global accelerators
	// first system
	wxAcceleratorEntry_v accels = sys_accels;

	// then user overrides
	// silently keep only last defined binding
	// same horribly inefficent O(n*m) search for duplicates as above..
	for (int i = 0; i < user_accels.size(); i++)
	{
		const wxAcceleratorEntry &ae = user_accels[i];

		for (wxAcceleratorEntry_v::iterator e = accels.begin(); e < accels.end(); e++)
			if (ae.GetFlags() == e->GetFlags() &&
			        ae.GetKeyCode() == e->GetKeyCode())
			{
				accels.erase(e);
				break;
			}

		accels.push_back(ae);
	}

	return accels;
}

void MainFrame::set_global_accels()
{
	wxAcceleratorEntry_v accels = get_accels(gopts.accels);
	// this is needed on Wine/win32 to support accels for close & quit
	wxGetApp().accels = accels;

	// Update menus; this probably takes the longest
	// as a side effect, any system-defined accels that weren't already in
	// the menus will be added now

	// first, zero out menu item on all accels
	for (int i = 0; i < accels.size(); i++)
		accels[i].Set(accels[i].GetFlags(), accels[i].GetKeyCode(),
		              accels[i].GetCommand());

	// yet another O(n*m) loop.  I really ought to sort the accel arrays
	for (int i = 0; i < ncmds; i++)
	{
		wxMenuItem* mi = cmdtab[i].mi;

		if (!mi)
			continue;

		// only *last* accelerator is made visible in menu
		// and is flagged as such by setting menu item in accel
		// the last is chosen so menu overrides non-menu and user overrides
		// system
		int cmd = cmdtab[i].cmd_id;
		int last_accel = -1;

		for (int j = 0; j < accels.size(); j++)
			if (cmd == accels[j].GetCommand())
				last_accel = j;

		if (last_accel >= 0)
		{
			DoSetAccel(mi, &accels[last_accel]);
			accels[last_accel].Set(accels[last_accel].GetFlags(),
			                       accels[last_accel].GetKeyCode(),
			                       accels[last_accel].GetCommand(), mi);
		}
		else
			// clear out user-cleared menu items
			DoSetAccel(mi, NULL);
	}

	// Finally, install a global accelerator table for any non-menu accels
	int len = 0;

	for (int i = 0; i < accels.size(); i++)
		if (!accels[i].GetMenuItem())
			len++;

	if (len)
	{
		wxAcceleratorEntry tab[1000];

		for (int i = 0, j = 0; i < accels.size(); i++)
			if (!accels[i].GetMenuItem())
				tab[j++] = accels[i];

		wxAcceleratorTable atab(len, tab);
		// set the table on the panel, where focus usually is
		// otherwise accelerators are lost sometimes
		panel->SetAcceleratorTable(atab);
	}
	else
		panel->SetAcceleratorTable(wxNullAcceleratorTable);

	// save recent accels
	for (int i = 0; i < 10; i++)
		recent_accel[i] = wxAcceleratorEntry();

	for (int i = 0; i < accels.size(); i++)
		if (accels[i].GetCommand() >= wxID_FILE1 &&
		        accels[i].GetCommand() <= wxID_FILE10)
			recent_accel[accels[i].GetCommand() - wxID_FILE1] = accels[i];

	SetRecentAccels();
}

void MainFrame::MenuOptionBool(const char* menuName, bool &field)
{
	int id = wxXmlResource::GetXRCID(wxString(menuName, wxConvUTF8));

	for (int i = 0; i < checkable_mi.size(); i++)
	{
		if (checkable_mi[i].cmd != id)
			continue;

		checkable_mi[i].boolopt = &field;
		checkable_mi[i].mi->Check(field);
		break;
	}
}

void MainFrame::MenuOptionIntMask(const char* menuName, int &field, int mask)
{
	int id = wxXmlResource::GetXRCID(wxString(menuName, wxConvUTF8));
	int value = mask;

	for (int i = 0; i < checkable_mi.size(); i++)
	{
		if (checkable_mi[i].cmd != id)
			continue;

		checkable_mi[i].intopt = &field;
		checkable_mi[i].mask = mask;
		checkable_mi[i].val = value;
		checkable_mi[i].mi->Check((field & mask) == value);
		break;
	}
}

void MainFrame::MenuOptionIntRadioValue(const char* menuName, int &field, int value)
{
	int id = wxXmlResource::GetXRCID(wxString(menuName, wxConvUTF8));

	for (int i = 0; i < checkable_mi.size(); i++)
	{
		if (checkable_mi[i].cmd != id)
			continue;

		checkable_mi[i].intopt = &field;
		checkable_mi[i].val = field;
		checkable_mi[i].mi->Check(field == value);
		break;
	}
}

// If there is a menubar, store all special menuitems
#define XRCITEM_I(id) menubar->FindItem(id, NULL)
#define XRCITEM_D(s) XRCITEM_I(XRCID_D(s))
#define XRCITEM(s) XRCITEM_D(wxT(s))

bool MainFrame::BindControls()
{
	// Make sure display panel present and correct type
	panel = XRCCTRL(*this, "DisplayArea", GameArea);

	if (!panel)
	{
		wxLogError(_("Main display panel not found"));
		return false;
	}

	panel->AdjustSize(false);
	// only the panel does idle events (the emulator loop)
	// however, do not enable until end of init, since errors will start
	// the idle loop on wxGTK
	wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED);
	// could/should probably take this from xrc as well
	// but I don't think xrc supports icon from Windows resource
	wxIcon icon = wxXmlResource::Get()->LoadIcon(wxT("MainIcon"));

	if (!icon.IsOk())
	{
		wxLogInfo(_("Main icon not found"));
		icon = wxICON(wxvbam);
	}

	SetIcon(icon);
	// NOOP if no status area
	SetStatusText(wxT(""));

	// Prepare system accel table
	for (int i = 0; i < num_def_accels; i++)
		sys_accels.push_back(default_accels[i]);

	wxMenuBar* menubar = GetMenuBar();
	ctx_menu = NULL;

	if (menubar)
	{
#if 0 // doesn't work in 2.9 at all (causes main menu to malfunction)
		// to fix, recursively copy entire menu insted of just copying
		// menubar.  This means that every saved menu item must also be
		// saved twice...  A lot of work for a mostly worthless feature.
		// If you want the menu, just exit full-screen mode.
		// Either that, or add an option to retain the regular
		// menubar in full-screen mode
		// create a context menu for fullscreen mode
		// FIXME: on gtk port, this gives Gtk-WARNING **:
		//   gtk_menu_attach_to_widget(): menu already attached to GtkMenuItem
		// but it works anyway
		// Note: menu default accelerators (e.g. alt-f for file menu) don't
		// work with context menu (and can never work, since there is no
		// way to pop up a submenu)
		// It would probably be better, in the end, to use a collapsed menu
		// bar (either Amiga-style press RMB to make appear, or Windows
		// collapsed toolbar-style move mouse to within a pixel of top to
		// make appear).  Not supported in wx without a lot of work, though.
		// Maybe this feature should just be dropped; the user would simply
		// have to exit fullscreen mode to use the menu.
		ctx_menu = new wxMenu();

		for (int i = 0; i < menubar->GetMenuCount(); i++)
			ctx_menu->AppendSubMenu(menubar->GetMenu(i), menubar->GetMenuLabel(i));

#endif

		// save all menu items in the command table
		for (int i = 0; i < ncmds; i++)
		{
			wxMenuItem* mi = cmdtab[i].mi = XRCITEM_I(cmdtab[i].cmd_id);
			// remove unsupported commands first
#ifdef NO_FFMPEG

			if (cmdtab[i].mask_flags & (CMDEN_SREC | CMDEN_NSREC | CMDEN_VREC | CMDEN_NVREC))
			{
				if (mi)
					mi->GetMenu()->Remove(mi);

				cmdtab[i].cmd_id = XRCID("NOOP");
				cmdtab[i].mi = NULL;
				continue;
			}

#endif
#ifndef GBA_LOGGING

			if (cmdtab[i].cmd_id == XRCID("Logging"))
			{
				if (mi)
					mi->GetMenu()->Remove(mi);

				cmdtab[i].cmd_id = XRCID("NOOP");
				cmdtab[i].mi = NULL;
				continue;
			}

#endif
#ifdef NO_LINK

			if (cmdtab[i].cmd_id == XRCID("LanLink") ||
			        cmdtab[i].cmd_id == XRCID("LinkType0Nothing") ||
			        cmdtab[i].cmd_id == XRCID("LinkType1Cable") ||
			        cmdtab[i].cmd_id == XRCID("LinkType2Wireless") ||
			        cmdtab[i].cmd_id == XRCID("LinkType3GameCube") ||
			        cmdtab[i].cmd_id == XRCID("LinkType4Gameboy") ||
			        cmdtab[i].cmd_id == XRCID("LinkAuto") ||
			        cmdtab[i].cmd_id == XRCID("SpeedOn") ||
			        cmdtab[i].cmd_id == XRCID("LinkProto") ||
			        cmdtab[i].cmd_id == XRCID("LinkConfigure")
			   )
			{
				if (mi)
					mi->GetMenu()->Remove(mi);

				cmdtab[i].cmd_id = XRCID("NOOP");
				cmdtab[i].mi = NULL;
				continue;
			}

#endif

			if (mi)
			{
				// wxgtk provides no way to retrieve stock label/accel
				// and does not override wxGetStockLabel()
				// as of 2.8.12/2.9.1
				// so override with wx's stock label  <sigh>
				// at least you still get gtk's stock icon
				if (mi->GetItemLabel().empty())
					mi->SetItemLabel(wxGetStockLabel(mi->GetId(),
					                                 wxSTOCK_WITH_MNEMONIC | wxSTOCK_WITH_ACCELERATOR));

				// add accelerator to global accel table
				wxAcceleratorEntry* a = mi->GetAccel();

				if (a)
				{
					a->Set(a->GetFlags(), a->GetKeyCode(), cmdtab[i].cmd_id, mi);

					// only add it if not already there
					for (wxAcceleratorEntry_v::iterator e = sys_accels.begin();
					        e < sys_accels.end(); e++)
						if (a->GetFlags() == e->GetFlags() &&
						        a->GetKeyCode() == e->GetKeyCode())
						{
							if (e->GetMenuItem())
							{
								wxLogInfo(_("Duplicate menu accelerator: %s for %s and %s; keeping first"),
								          wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).c_str(),
								          e->GetMenuItem()->GetItemLabelText().c_str(),
								          mi->GetItemLabelText().c_str());
								delete a;
								a = 0;
							}
							else
							{
								if (e->GetCommand() != a->GetCommand())
								{
									int cmd;

									for (cmd = 0; cmd < ncmds; cmd++)
										if (cmdtab[cmd].cmd_id == e->GetCommand())
											break;

									wxLogInfo(_("Menu accelerator %s for %s overrides default for %s ; keeping menu"),
									          wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).c_str(),
									          mi->GetItemLabelText().c_str(),
									          cmdtab[cmd].cmd);
								}

								sys_accels.erase(e);
							}

							break;
						}

					if (a)
						sys_accels.push_back(*a);
					else
						// strip from label so user isn't confused
						DoSetAccel(mi, NULL);
				}

				// store checkable items
				if (mi->IsCheckable())
				{
					checkable_mi_t cmi = { cmdtab[i].cmd_id, mi };
					checkable_mi.push_back(cmi);

					for (int j = 0; j < num_opts; j++)
					{
						wxString menuName = wxString(opts[j].cmd, wxConvUTF8);

						if (menuName == cmdtab[i].cmd)
						{
							if (opts[j].intopt)
								MenuOptionIntMask(opts[j].cmd, *opts[j].intopt, (1 << 0));
							else if (opts[j].boolopt)
								MenuOptionBool(opts[j].cmd, *opts[j].boolopt);
						}
					}
				}
			}
		}

		// if a recent menu is present, save its location
		wxMenuItem* recentmi = XRCITEM("RecentMenu");

		if (recentmi && recentmi->IsSubMenu())
		{
			recent = recentmi->GetSubMenu();
			gopts.recent->UseMenu(recent);
			gopts.recent->AddFilesToMenu();
		}
		else
			recent = NULL;

		// if save/load state menu items present, save their locations
		for (int i = 0; i < 10; i++)
		{
			wxString n;
			n.Printf(wxT("LoadGame%02d"), i + 1);
			loadst_mi[i] = XRCITEM_D(n);
			n.Printf(wxT("SaveGame%02d"), i + 1);
			savest_mi[i] = XRCITEM_D(n);
		}
	}
	else
	{
		recent = NULL;

		for (int i = 0; i < 10; i++)
			loadst_mi[i] = savest_mi[i] = NULL;
	}

	// just setting to UNLOAD_CMDEN_KEEP is invalid
	// so just set individual flags here
	cmd_enable = CMDEN_NGDB_ANY | CMDEN_NREC_ANY;
	update_state_ts(true);

	// set pointers for checkable menu items
	// and set initial checked status
	if (checkable_mi.size())
	{
		MenuOptionBool("RecentFreeze", gopts.recent_freeze);
		MenuOptionBool("Pause", paused);
		MenuOptionIntMask("SoundChannel1", gopts.sound_en, (1 << 0));
		MenuOptionIntMask("SoundChannel2", gopts.sound_en, (1 << 1));
		MenuOptionIntMask("SoundChannel3", gopts.sound_en, (1 << 2));
		MenuOptionIntMask("SoundChannel4", gopts.sound_en, (1 << 3));
		MenuOptionIntMask("DirectSoundA", gopts.sound_en, (1 << 8));
		MenuOptionIntMask("DirectSoundB", gopts.sound_en, (1 << 9));
		MenuOptionIntMask("VideoLayersBG0", layerSettings, (1 << 8));
		MenuOptionIntMask("VideoLayersBG1", layerSettings, (1 << 9));
		MenuOptionIntMask("VideoLayersBG2", layerSettings, (1 << 10));
		MenuOptionIntMask("VideoLayersBG3", layerSettings, (1 << 11));
		MenuOptionIntMask("VideoLayersOBJ", layerSettings, (1 << 12));
		MenuOptionIntMask("VideoLayersWIN0", layerSettings, (1 << 13));
		MenuOptionIntMask("VideoLayersWIN1", layerSettings, (1 << 14));
		MenuOptionIntMask("VideoLayersOBJWIN", layerSettings, (1 << 15));
		MenuOptionBool("CheatsAutoSaveLoad", gopts.autoload_cheats);
		MenuOptionIntMask("CheatsEnable", cheatsEnabled, 1);
		MenuOptionIntMask("KeepSaves", skipSaveGameBattery, 1);
		MenuOptionIntMask("KeepCheats", skipSaveGameCheats, 1);
		MenuOptionBool("LoadGameAutoLoad", gopts.autoload_state);
		MenuOptionIntMask("JoypadAutofireA", autofire, KEYM_A);
		MenuOptionIntMask("JoypadAutofireB", autofire, KEYM_B);
		MenuOptionIntMask("JoypadAutofireL", autofire, KEYM_LEFT);
		MenuOptionIntMask("JoypadAutofireR", autofire, KEYM_RIGHT);
		MenuOptionBool("EmulatorSpeedupToggle", turbo);
		MenuOptionIntRadioValue("LinkType0Nothing", gopts.gba_link_type, 0);
		MenuOptionIntRadioValue("LinkType1Cable", gopts.gba_link_type, 1);
		MenuOptionIntRadioValue("LinkType2Wireless", gopts.gba_link_type, 2);
		MenuOptionIntRadioValue("LinkType3GameCube", gopts.gba_link_type, 3);
		MenuOptionIntRadioValue("LinkType4Gameboy", gopts.gba_link_type, 4);
	}

	for (int i = 0; i < checkable_mi.size(); i++)
		if (!checkable_mi[i].boolopt && !checkable_mi[i].intopt)
		{
			wxLogError(_("Invalid menu item %s; removing"),
			           checkable_mi[i].mi->GetItemLabelText().c_str());
			checkable_mi[i].mi->GetMenu()->Remove(checkable_mi[i].mi);
			checkable_mi[i].mi = NULL;
		}

	set_global_accels();

	// preload and verify all resource dialogs
	// this will take init time and memory, but catches errors in xrc sooner
	// note that the only verification done is to ensure no crashes.  It's the
	// user's responsibility to ensure that the GUI works as intended after
	// modifications
	try
	{
		wxDialog* d = NULL;
		//// displayed during run
		d = LoadXRCDialog("GBPrinter");
		// just verify preview window & mag sel present
		{
			wxPanel* prev;
			prev = SafeXRCCTRL<wxPanel>(d, "Preview");

			if (!wxDynamicCast(prev->GetParent(), wxScrolledWindow))
				throw std::runtime_error("Unable to load a dialog control from the builtin xrc file: Preview");

			SafeXRCCTRL<wxControlWithItems>(d, "Magnification");
			d->Fit();
		}
		//// File menu
		d = LoadXRCDialog("GBAROMInfo");
		// just verify fields present
		wxControl* lab;
#define getlab(n) lab=SafeXRCCTRL<wxControl>(d,  n)
		getlab("Title");
		getlab("GameCode");
		getlab("MakerCode");
		getlab("MakerName");
		getlab("UnitCode");
		getlab("DeviceType");
		getlab("Version");
		getlab("CRC");
		d->Fit();
		d = LoadXRCDialog("GBROMInfo");
		// just verify fields present
		getlab("Title");
		getlab("MakerCode");
		getlab("MakerName");
		getlab("UnitCode");
		getlab("DeviceType");
		getlab("Version");
		getlab("CRC");
		getlab("Color");
		getlab("ROMSize");
		getlab("RAMSize");
		getlab("DestCode");
		getlab("LicCode");
		getlab("Checksum");
		d->Fit();
		d = LoadXRCDialog("CodeSelect");
		// just verify list present
		SafeXRCCTRL<wxControlWithItems>(d, "CodeList");
		d->Fit();
		d = LoadXRCDialog("ExportSPS");
		// just verify text fields present
		SafeXRCCTRL<wxTextCtrl>(d, "Title");
		SafeXRCCTRL<wxTextCtrl>(d, "Description");
		SafeXRCCTRL<wxTextCtrl>(d, "Notes");
		d->Fit();
		//// Emulation menu
#ifndef NO_LINK
		d = LoadXRCDialog("NetLink");
#endif
		wxRadioButton* rb;
#define getrbi(n, o, v) do { \
    rb=SafeXRCCTRL<wxRadioButton>(d,  n); \
    rb->SetValidator(wxBoolIntValidator(&o, v)); \
        } while(0)
#define getrbb(n, o) do { \
    rb=SafeXRCCTRL<wxRadioButton>(d,  n); \
    rb->SetValidator(wxGenericValidator(&o)); \
        } while(0)
#define getrbbr(n, o) do { \
    rb=SafeXRCCTRL<wxRadioButton>(d,  n); \
    rb->SetValidator(wxBoolRevValidator(&o)); \
        } while(0)
		wxBoolEnValidator* benval;
		wxBoolEnHandler* ben;
#define getbe(n, o, cv, t, wt) do { \
    cv=SafeXRCCTRL<t>(d,  n); \
    cv->SetValidator(wxBoolEnValidator(&o)); \
    benval = wxStaticCast(cv->GetValidator(), wxBoolEnValidator); \
    static wxBoolEnHandler _ben; \
    ben = &_ben; \
    wx##wt##BoolEnHandlerConnect(cv, wxID_ANY, _ben); \
        } while(0)
		// brenval & friends are here just to allow yes/no radioboxes in place
		// of checkboxes.  A lot of work for little benefit.
		wxBoolRevEnValidator* brenval;
#define getbre(n, o, cv, t, wt) do { \
    cv=SafeXRCCTRL<t>(d,  n); \
    cv->SetValidator(wxBoolRevEnValidator(&o)); \
    brenval = wxStaticCast(cv->GetValidator(), wxBoolRevEnValidator); \
    wx##wt##BoolEnHandlerConnect(rb, wxID_ANY, *ben); \
        } while(0)
#define addbe(n) do { \
    ben->controls.push_back(n); \
    benval->controls.push_back(n); \
        } while(0)
#define addrbe(n) do { \
    addbe(n); \
    brenval->controls.push_back(n); \
        } while(0)
#define addber(n, r) do { \
    ben->controls.push_back(n); \
    ben->reverse.push_back(r); \
    benval->controls.push_back(n); \
    benval->reverse.push_back(r); \
        } while(0)
#define addrber(n, r) do { \
    addber(n, r); \
    brenval->controls.push_back(n); \
    brenval->reverse.push_back(r); \
        } while(0)
#define getrbbe(n, o) getbe(n, o, rb, wxRadioButton, RBE)
#define getrbbd(n, o) getbre(n, o, rb, wxRadioButton, RBD)
		wxTextCtrl* tc;
#define gettc(n, o) do { \
    tc=SafeXRCCTRL<wxTextCtrl>(d,  n); \
    tc->SetValidator(wxTextValidator(wxFILTER_NONE, &o)); \
        } while(0)
#ifndef NO_LINK
		{
			net_link_handler.dlg = d;
			net_link_handler.n_players = linkNumPlayers;
			getrbbe("Server", net_link_handler.server);
			getrbbd("Client", net_link_handler.server);
			getlab("PlayersLab");
			addrber(lab, false);
			getrbi("Link2P", net_link_handler.n_players, 2);
			addrber(rb, false);
			getrbi("Link3P", net_link_handler.n_players, 3);
			addrber(rb, false);
			getrbi("Link4P", net_link_handler.n_players, 4);
			addrber(rb, false);
			getlab("ServerIPLab");
			addrber(lab, true);
			gettc("ServerIP", gopts.link_host);
			addrber(tc, true);
			wxWindow* okb = d->FindWindow(wxID_OK);

			if (okb)   // may be gone if style guidlines removed it
			{
				net_link_handler.okb = wxStaticCast(okb, wxButton);
				d->Connect(XRCID("Server"), wxEVT_COMMAND_RADIOBUTTON_SELECTED,
				           wxCommandEventHandler(NetLink_t::ServerOKButton),
				           NULL, &net_link_handler);
				d->Connect(XRCID("Client"), wxEVT_COMMAND_RADIOBUTTON_SELECTED,
				           wxCommandEventHandler(NetLink_t::ClientOKButton),
				           NULL, &net_link_handler);
			}

			// this should intercept wxID_OK before the dialog handler gets it
			d->Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(NetLink_t::NetConnect),
			           NULL, &net_link_handler);
			d->Fit();
		}
#endif
		d = LoadXRCDialog("CheatList");
		{
			cheat_list_handler.dlg = d;
			d->SetEscapeId(wxID_OK);
			wxCheckedListCtrl* cl;
			cl = SafeXRCCTRL<wxCheckedListCtrl>(d, "Cheats");

			if (!cl->Init())
				throw std::runtime_error("Unable to initialize the Cheats dialog control from the builtin xrc file!");

			cheat_list_handler.list = cl;
			cl->SetValidator(CheatListFill());
			cl->InsertColumn(0, _("Code"));
			// can't just set font for whole column; must set in each
			// individual item
			wxFont of = cl->GetFont();
			// of.SetFamily(wxFONTFAMILY_MODERN);  // doesn't work (no font change)
			wxFont f(of.GetPointSize(), wxFONTFAMILY_MODERN, of.GetStyle(),
			         of.GetWeight());
			cheat_list_handler.item0.SetFont(f);
			cheat_list_handler.item0.SetColumn(0);
			cl->InsertColumn(1, _("Description"));
			// too bad I can't just set the size to windowwidth - other cols
			// default width is header width, but using following will probably
			// make it 80 pixels wide regardless
			// cl->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER);
			cheat_list_handler.col1minw = cl->GetColumnWidth(1);
			// on wxGTK, column 1 seems to inherit column 0's font regardless
			// of requested font
			cheat_list_handler.item1.SetFont(cl->GetFont());
			cheat_list_handler.item1.SetColumn(1);
#if 0
			// the ideal way to set col 0's width would be to use
			// wxLIST_AUTOSIZE after setting value to a sample:
			cheat_list_handler.item0.SetText(wxT("00000000 00000000"));
			cl->InsertItem(cheat_list_handler.item0);
			cl->SetColumnWidth(0, wxLIST_AUTOSIZE);
			cl->RemoveItem(0);
#else
			// however, the generic listctrl implementation uses the wrong
			// font to determine width (window vs. item), and does not
			// calculate the margins the same way in calculation vs. actual
			// drawing.  so calculate manually, using knowledge of underlying
			// code.  This is highly version-unportable, but better than using
			// buggy wx code..
			int w, h;
			cl->GetImageList(wxIMAGE_LIST_SMALL)->GetSize(0, w, h);
			w += 5; // IMAGE_MARGIN_IN_REPORT_MODE
			// following is missing from wxLIST_AUTOSIZE
			w += 8; // ??? subtracted from width avail for text
			{
				int charwidth, charheight;
				wxClientDC dc(cl);
				// following is item font instead of window font,
				// and so is missing from wxLIST_AUTOSIZE
				dc.SetFont(f);
				dc.GetTextExtent(wxT('M'), &charwidth, &charheight);
				w += (8 + 1 + 8) * charwidth;
			}
			cl->SetColumnWidth(0, w);
#endif
			d->Connect(wxEVT_COMMAND_TOOL_CLICKED,
			           wxCommandEventHandler(CheatList_t::Tool),
			           NULL, &cheat_list_handler);
			d->Connect(wxEVT_COMMAND_LIST_ITEM_CHECKED,
			           wxListEventHandler(CheatList_t::Check),
			           NULL, &cheat_list_handler);
			d->Connect(wxEVT_COMMAND_LIST_ITEM_UNCHECKED,
			           wxListEventHandler(CheatList_t::UnCheck),
			           NULL, &cheat_list_handler);
			d->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED,
			           wxListEventHandler(CheatList_t::Edit),
			           NULL, &cheat_list_handler);
			d->Fit();
		}
		d = LoadXRCDialog("CheatEdit");
		wxChoice* ch;
		{
			// d->Reparent(cheat_list_handler.dlg); // broken
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Type", wxGenericValidator(&cheat_list_handler.ce_type));
			cheat_list_handler.ce_type_ch = ch;
			gettc("Desc", cheat_list_handler.ce_desc);
			tc->SetMaxLength(sizeof(cheatsList[0].desc) - 1);
			gettc("Codes", cheat_list_handler.ce_codes);
			cheat_list_handler.ce_codes_tc = tc;
			d->Fit();
		}
		d = LoadXRCDialog("CheatCreate");
		{
			cheat_find_handler.dlg = d;
			d->SetEscapeId(wxID_OK);
			CheatListCtrl* list;
			list = SafeXRCCTRL<CheatListCtrl>(d, "CheatList");
			cheat_find_handler.list = list;
			list->SetValidator(CheatFindFill());
			list->InsertColumn(0, _("Address"));
			list->InsertColumn(1, _("Old Value"));
			list->InsertColumn(2, _("New Value"));
			getrbi("EQ", cheat_find_handler.op, SEARCH_EQ);
			getrbi("NE", cheat_find_handler.op, SEARCH_NE);
			getrbi("LT", cheat_find_handler.op, SEARCH_LT);
			getrbi("LE", cheat_find_handler.op, SEARCH_LE);
			getrbi("GT", cheat_find_handler.op, SEARCH_GT);
			getrbi("GE", cheat_find_handler.op, SEARCH_GE);
#define cf_make_update() \
    rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \
        wxCommandEventHandler(CheatFind_t::UpdateView), \
        NULL, &cheat_find_handler)
			getrbi("Size8", cheat_find_handler.size, BITS_8);
			cf_make_update();
			getrbi("Size16", cheat_find_handler.size, BITS_16);
			cf_make_update();
			getrbi("Size32", cheat_find_handler.size, BITS_32);
			cf_make_update();
			getrbi("Signed", cheat_find_handler.fmt, CFVFMT_SD);
			cf_make_update();
			getrbi("Unsigned", cheat_find_handler.fmt, CFVFMT_UD);
			cf_make_update();
			getrbi("Hexadecimal", cheat_find_handler.fmt, CFVFMT_UH);
			cf_make_update();
#define cf_make_valen() \
    rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \
        wxCommandEventHandler(CheatFind_t::EnableVal), \
        NULL, &cheat_find_handler)
			getrbi("OldValue", cheat_find_handler.valsrc, 1);
			cf_make_valen();
			cheat_find_handler.old_rb = rb;
			rb->Disable();
			getrbi("SpecificValue", cheat_find_handler.valsrc, 0);
			cf_make_valen();
			cheat_find_handler.val_rb = rb;
			gettc("Value", cheat_find_handler.val_s);
			cheat_find_handler.val_tc = tc;
			wxStaticCast(tc->GetValidator(), wxTextValidator)->SetStyle(wxFILTER_INCLUDE_CHAR_LIST);
#define cf_button(n, f) \
    d->Connect(XRCID(n), wxEVT_COMMAND_BUTTON_CLICKED, \
           wxCommandEventHandler(CheatFind_t::f), \
           NULL, &cheat_find_handler);
#define cf_enbutton(n, v) do { \
    cheat_find_handler.v=SafeXRCCTRL<wxButton>(d,  n); \
    cheat_find_handler.v->Disable(); \
            } while(0)
			cf_button("Search", Search);
			cf_button("Update", UpdateVals);
			cf_enbutton("Update", update_b);
			cf_button("Clear", ResetSearch);
			cf_enbutton("Clear", clear_b);
			cf_button("AddCheat", AddCheatB);
			cf_enbutton("AddCheat", add_b);
			d->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED,
			           wxListEventHandler(CheatFind_t::AddCheatL),
			           NULL, &cheat_find_handler);
			d->Connect(wxEVT_COMMAND_LIST_ITEM_SELECTED,
			           wxListEventHandler(CheatFind_t::Select),
			           NULL, &cheat_find_handler);
			d->Fit();
		}
		d = LoadXRCDialog("CheatAdd");
		{
			// d->Reparent(cheat_find_handler.dlg); // broken
			gettc("Desc", cheat_find_handler.ca_desc);
			tc->SetMaxLength(sizeof(cheatsList[0].desc) - 1);
			gettc("Value", cheat_find_handler.ca_val);
			cheat_find_handler.ca_val_tc = tc;
			// MFC interface used this for cheat list's generic code adder as well,
			// and made format selectable in interface.  I think the plain
			// interface is good enough, even though the format for GB cheats
			// is non-obvious.  Therefore, the format is now just a read-only
			// field.
			getlab("Format");
			cheat_find_handler.ca_fmt = lab;
			getlab("Address");
			cheat_find_handler.ca_addr = lab;
			d->Fit();
		}
		//// config menu
		d = LoadXRCDialog("GeneralConfig");
		wxCheckBox* cb;
#define getcbb(n, o) do { \
    cb=SafeXRCCTRL<wxCheckBox>(d, n); \
    cb->SetValidator(wxGenericValidator(&o)); \
        } while(0)
#define getcbi(n, o) do { \
    cb=SafeXRCCTRL<wxCheckBox>(d, n); \
    cb->SetValidator(wxBoolIntValidator(&o, 1)); \
        } while(0)
		wxSpinCtrl* sc;
#define getsc(n, o) do { \
    sc=SafeXRCCTRL<wxSpinCtrl>(d, n); \
    sc->SetValidator(wxGenericValidator(&o)); \
        } while(0)
		{
			// Online Auto Update check frequency
			getrbi("UpdateNever", gopts.onlineupdates, 0);
			getrbi("UpdateDaily", gopts.onlineupdates, 1);
			getrbi("UpdateWeekly", gopts.onlineupdates, 7);
			getrbi("PNG", captureFormat, 0);
			getrbi("BMP", captureFormat, 1);
			getsc("RewindInterval", gopts.rewind_interval);
			getsc("Throttle", throttle);
			throttle_ctrl.thr = sc;
			throttle_ctrl.thrsel = SafeXRCCTRL<wxChoice>(d, "ThrottleSel");
			throttle_ctrl.thr->
			Connect(wxEVT_COMMAND_SPINCTRL_UPDATED,
			        wxSpinEventHandler(ThrottleCtrl_t::SetThrottleSel),
			        NULL, &throttle_ctrl);
			throttle_ctrl.thrsel->
			Connect(wxEVT_COMMAND_CHOICE_SELECTED,
			        wxCommandEventHandler(ThrottleCtrl_t::SetThrottle),
			        NULL, &throttle_ctrl);
			d->Connect(wxEVT_SHOW, wxShowEventHandler(ThrottleCtrl_t::Init),
			           NULL, &throttle_ctrl);
			d->Fit();
		}
#define getcbbe(n, o) getbe(n, o, cb, wxCheckBox, CB)
		wxBoolIntEnValidator* bienval;
#define getbie(n, o, v, cv, t, wt) do { \
    cv=SafeXRCCTRL<t>(d, n); \
    cv->SetValidator(wxBoolIntEnValidator(&o, v, v)); \
    bienval = wxStaticCast(cv->GetValidator(), wxBoolIntEnValidator); \
    static wxBoolEnHandler _ben; \
    ben = &_ben; \
    wx##wt##BoolEnHandlerConnect(cv, wxID_ANY, _ben); \
        } while(0)
#define addbie(n) do { \
    ben->controls.push_back(n); \
    bienval->controls.push_back(n); \
        } while(0)
#define addbier(n, r) do { \
    ben->controls.push_back(n); \
    ben->reverse.push_back(r); \
    bienval->controls.push_back(n); \
    bienval->reverse.push_back(r); \
        } while(0)
#define getcbie(n, o, v) getbie(n, o, v, cb, wxCheckBox, CB)
		wxFilePickerCtrl* fp;
#define getfp(n, o) do { \
    fp=SafeXRCCTRL<wxFilePickerCtrl>(d, n); \
    fp->SetValidator(wxFileDirPickerValidator(&o)); \
        } while(0)
		d = LoadXRCropertySheetDialog("GameBoyConfig");
		{
			/// System and Peripherals
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "System", wxGenericValidator(&gbEmulatorType));
			// "Display borders" corresponds to 2 variables, so it is handled
			// in command handler.  Plus making changes might require resizing
			// game area.  Validation only here.
			SafeXRCCTRL<wxChoice>(d, "Borders");
			/// Boot ROM
			getfp("BootRom", gopts.gb_bios);
			getlab("BootRomLab");
			getfp("CBootRom", gopts.gbc_bios);
			getlab("CBootRomLab");
			/// Custom Colors
			//getcbi("Color", gbColorOption);
			wxFarRadio* r = NULL;

			for (int i = 0; i < 3; i++)
			{
				wxString pn;
				// NOTE: wx2.9.1 behaves differently for referenced nodes
				// than 2.8!  Unless there is an actual child node, the ID field
				// will not be overwritten.  This means that there should be a
				// dummy child node (e.g. position=(0,0)).  If you get
				// "Unable to load dialog GameBoyConfig from resources", this is
				// probably the reason.
				pn.Printf(wxT("cp%d"), i + 1);
				wxWindow* w = SafeXRCCTRL<wxWindow>(d, ToString(pn).c_str());
				GBColorConfigHandler[i].p = w;
				GBColorConfigHandler[i].pno = i;
				wxFarRadio* cb = SafeXRCCTRL<wxFarRadio>(w, "UsePalette");

				if (r)
					cb->SetGroup(r);
				else
					r = cb;

				cb->SetValidator(wxBoolIntValidator(&gbPaletteOption, i));
				ch = SafeXRCCTRL<wxChoice>(w, "ColorSet");
				GBColorConfigHandler[i].c = ch;

				for (int j = 0; j < 8; j++)
				{
					wxString s;
					s.Printf(wxT("Color%d"), j);
					wxColourPickerCtrl* cp = SafeXRCCTRL<wxColourPickerCtrl>(w, ToString(s).c_str());
					GBColorConfigHandler[i].cp[j] = cp;
					cp->SetValidator(wxColorValidator(&systemGbPalette[i * 8 + j]));
				}

				w->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
				           wxCommandEventHandler(GBColorConfig_t::ColorSel),
				           NULL, &GBColorConfigHandler[i]);
				w->Connect(XRCID("Reset"), wxEVT_COMMAND_BUTTON_CLICKED,
				           wxCommandEventHandler(GBColorConfig_t::ColorReset),
				           NULL, &GBColorConfigHandler[i]);
				w->Connect(wxID_ANY, wxEVT_COMMAND_COLOURPICKER_CHANGED,
				           wxCommandEventHandler(GBColorConfig_t::ColorButton),
				           NULL, &GBColorConfigHandler[i]);
			}

			d->Fit();
		}
		d = LoadXRCropertySheetDialog("GameBoyAdvanceConfig");
		{
			/// System and peripherals
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "SaveType", wxGenericValidator(&cpuSaveType));
			BatConfigHandler.type = ch;
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "FlashSize", wxGenericValidator(&winFlashSize));
			BatConfigHandler.size = ch;
			d->Connect(XRCID("SaveType"), wxEVT_COMMAND_CHOICE_SELECTED,
			           wxCommandEventHandler(BatConfig_t::ChangeType),
			           NULL, &BatConfigHandler);
#define getgbaw(n) do { \
    wxWindow *w = d->FindWindow(XRCID(n)); \
    CheckThrowXRCError(w,n); \
    w->SetValidator(GBACtrlEnabler()); \
            } while(0)
			getgbaw("Detect");
			d->Connect(XRCID("Detect"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(BatConfig_t::Detect),
			           NULL, &BatConfigHandler);
			/// Boot ROM
			getfp("BootRom", gopts.gba_bios);
			getlab("BootRomLab");
			/// Game Overrides
			getgbaw("GameSettings");
			// the rest must be filled in by command handler; just validate
			SafeXRCCTRL<wxTextCtrl>(d, "Comment");
			SafeXRCCTRL<wxChoice>(d, "OvRTC");
			SafeXRCCTRL<wxChoice>(d, "OvSaveType");
			SafeXRCCTRL<wxChoice>(d, "OvFlashSize");
			SafeXRCCTRL<wxChoice>(d, "OvMirroring");
			d->Fit();
		}
		d = LoadXRCropertySheetDialog("DisplayConfig");
		{
			/// Speed
			// AutoSkip/FrameSkip are 2 controls for 1 value.  Needs post-process
			// to ensure checkbox not ignored
			getsc("FrameSkip", frameSkip);
			getlab("FrameSkipLab");
			int fs = frameSkip;

			if (fs >= 0)
				systemFrameSkip = fs;

			/// On-Screen Display
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "SpeedIndicator", wxGenericValidator(&showSpeed));
			/// Zoom
			// this was a choice, but I'd rather not have to make an off-by-one
			// validator just for this, and spinctrl is good enough.
			getsc("DefaultScale", gopts.video_scale);
			getsc("MaxScale", maxScale);
			/// Advanced
			getrbi("OutputSimple", gopts.render_method, RND_SIMPLE);
			getrbi("OutputOpenGL", gopts.render_method, RND_OPENGL);
#ifdef NO_OGL
			rb->Hide();
#endif
			getrbi("OutputCairo", gopts.render_method, RND_CAIRO);
#ifdef NO_CAIRO
			rb->Hide();
#endif
			getrbi("OutputDirect3D", gopts.render_method, RND_DIRECT3D);
#if !defined(__WXMSW__) || defined(NO_D3D) || 1 // not implemented
			rb->Hide();
#endif
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Filter", wxGenericValidator(&gopts.filter));
			// these two are filled and/or hidden at dialog load time
			wxControl* pll;
			wxChoice* pl;
			pll = SafeXRCCTRL<wxControl>(d, "PluginLab");
			pl = SafeXRCCTRL<wxChoice>(d, "Plugin");
			pll->SetValidator(PluginEnabler());
			pl->SetValidator(PluginListFiller(d, pll, ch));
			PluginEnableHandler.lab = pll;
			PluginEnableHandler.ch = pl;
			ch->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
			            wxCommandEventHandler(PluginEnable_t::ToggleChoice),
			            NULL, &PluginEnableHandler);
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "IFB", wxGenericValidator(&gopts.ifb));
			d->Fit();
		}
		d = LoadXRCropertySheetDialog("SoundConfig");
		wxSlider* sl;
#define getsl(n, o) do { \
    sl=SafeXRCCTRL<wxSlider>(d, n); \
    sl->SetValidator(wxGenericValidator(&o)); \
        } while(0)
		{
			/// Basic
			getsl("Volume", gopts.sound_vol);
			sound_config_handler.vol = sl;
			d->Connect(XRCID("Volume100"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(SoundConfig_t::FullVol),
			           NULL, &sound_config_handler);
			ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Rate", wxGenericValidator(&gopts.sound_qual));
			/// Advanced
#define audapi_rb(n, v) do {\
    getrbi(n, gopts.audio_api, v); \
    rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \
        wxCommandEventHandler(SoundConfig_t::SetAPI), \
        NULL, &sound_config_handler); \
            } while(0)
			audapi_rb("SDL", AUD_SDL);
			audapi_rb("OpenAL", AUD_OPENAL);
#ifdef NO_OAL
			rb->Hide();
#endif
			audapi_rb("DirectSound", AUD_DIRECTSOUND);
#ifndef __WXMSW__
			rb->Hide();
#endif
			audapi_rb("XAudio2", AUD_XAUDIO2);
#if !defined(__WXMSW__) || defined(NO_XAUDIO2)
			rb->Hide();
#endif
			sound_config_handler.dev = SafeXRCCTRL<wxChoice>(d, "Device");
			sound_config_handler.dev->SetValidator(SoundConfigLoad());
			getcbb("Upmix", gopts.upmix);
			sound_config_handler.umix = cb;
#if !defined(__WXMSW__) || defined(NO_XAUDIO2)
			cb->Hide();
#endif
			getcbb("HWAccel", gopts.dsound_hw_accel);
			sound_config_handler.hwacc = cb;
#ifndef __WXMSW__
			cb->Hide();
#endif
			getsl("Buffers", gopts.audio_buffers);
			sound_config_handler.bufs = sl;
			getlab("BuffersInfo");
			sound_config_handler.bufinfo = lab;
			sl->Connect(wxEVT_SCROLL_CHANGED,
			            wxCommandEventHandler(SoundConfig_t::AdjustFramesEv),
			            NULL, &sound_config_handler);
			sl->Connect(wxEVT_SCROLL_THUMBTRACK,
			            wxCommandEventHandler(SoundConfig_t::AdjustFramesEv),
			            NULL, &sound_config_handler);
			sound_config_handler.AdjustFrames(10);
			/// Game Boy
			wxPanel* p;
			p = SafeXRCCTRL<wxPanel>(d, "GBEnhanceSoundDep");
			getsl("GBEcho", gopts.gb_echo);
			getsl("GBStereo", gopts.gb_stereo);
			/// Game Boy Advance
			getsl("GBASoundFiltering", gopts.gba_sound_filter);
			d->Fit();
		}
		wxDirPickerCtrl* dp;
#define getdp(n, o) do { \
    dp=SafeXRCCTRL<wxDirPickerCtrl>(d, n); \
    dp->SetValidator(wxFileDirPickerValidator(&o)); \
        } while(0)
		d = LoadXRCDialog("DirectoriesConfig");
		{
			getdp("GBARoms", gopts.gba_rom_dir);
			getdp("GBRoms", gopts.gb_rom_dir);
			getdp("GBCRoms", gopts.gbc_rom_dir);
			getdp("BatSaves", gopts.battery_dir);
			getdp("StateSaves", gopts.state_dir);
			getdp("Screenshots", gopts.scrshot_dir);
			getdp("Recordings", gopts.recording_dir);
			d->Fit();
		}
		wxDialog* joyDialog = LoadXRCropertySheetDialog("JoypadConfig");
		wxFarRadio* r = 0;

		for (int i = 0; i < 4; i++)
		{
			wxString pn;
			// NOTE: wx2.9.1 behaves differently for referenced nodes
			// than 2.8!  Unless there is an actual child node, the ID field
			// will not be overwritten.  This means that there should be a
			// dummy child node (e.g. position=(0,0)).  If you get
			// "Unable to load dialog JoypadConfig from resources", this is
			// probably the reason.
			pn.Printf(wxT("joy%d"), i + 1);
			wxWindow* w = SafeXRCCTRL<wxWindow>(joyDialog, ToString(pn).c_str());
			wxFarRadio* cb;
			cb = SafeXRCCTRL<wxFarRadio>(w, "DefaultConfig");

			if (r)
				cb->SetGroup(r);
			else
				r = cb;

			cb->SetValidator(wxBoolIntValidator(&gopts.default_stick, i + 1));
			wxWindow* prev = NULL, *prevp = NULL;

			for (int j = 0; j < NUM_KEYS; j++)
			{
				wxJoyKeyTextCtrl* tc = XRCCTRL_D(*w, joynames[j], wxJoyKeyTextCtrl);
				CheckThrowXRCError(tc, ToString(joynames[j]));
				wxWindow* p = tc->GetParent();

				if (p == prevp)
					tc->MoveAfterInTabOrder(prev);

				prev = tc;
				prevp = p;
				tc->SetValidator(wxJoyKeyValidator(&gopts.joykey_bindings[i][j]));
			}

			JoyPadConfigHandler[i].p = w;
			w->Connect(XRCID("Defaults"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons),
			           NULL, &JoyPadConfigHandler[i]);
			w->Connect(XRCID("Clear"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons),
			           NULL, &JoyPadConfigHandler[i]);
			joyDialog->Fit();
		}

#ifndef NO_LINK
		d = LoadXRCDialog("LinkConfig");
		{
			getlab("LinkTimeoutLab");
			addbe(lab);
			getsc("LinkTimeout", linkTimeout);
			addbe(sc);
			d->Fit();
		}
#endif
		d = LoadXRCDialog("AccelConfig");
		{
			wxTreeCtrl* tc;
			tc = SafeXRCCTRL<wxTreeCtrl>(d, "Commands");
			accel_config_handler.tc = tc;
			wxControlWithItems* lb;
			lb = SafeXRCCTRL<wxControlWithItems>(d, "Current");
			accel_config_handler.lb = lb;
			accel_config_handler.asb = SafeXRCCTRL<wxButton>(d, "Assign");
			accel_config_handler.remb = SafeXRCCTRL<wxButton>(d, "Remove");
			accel_config_handler.key = SafeXRCCTRL<wxKeyTextCtrl>(d, "Shortcut");
			accel_config_handler.curas = SafeXRCCTRL<wxControl>(d, "AlreadyThere");
			accel_config_handler.key->MoveBeforeInTabOrder(accel_config_handler.asb);
			accel_config_handler.key->SetMultikey(0);
			accel_config_handler.key->SetClearable(false);
			wxTreeItemId rid = tc->AddRoot(wxT("root"));

			if (menubar)
			{
				wxTreeItemId mid = tc->AppendItem(rid, _("Menu commands"));

				for (int i = 0; i < menubar->GetMenuCount(); i++)
				{
#if wxCHECK_VERSION(2,8,8)
					wxTreeItemId id = tc->AppendItem(mid, menubar->GetMenuLabelText(i));
#else
					// 2.8.4 has no equivalent for GetMenuLabelText()
					wxString txt = menubar->GetMenuLabel(i);
					txt.Replace(wxT("&"), wxT(""));
					wxTreeItemId id = tc->AppendItem(mid, txt);
#endif
					add_menu_accels(tc, id, menubar->GetMenu(i));
				}
			}

			wxTreeItemId oid;
			int noop_id = XRCID("NOOP");

			for (int i = 0; i < ncmds; i++)
			{
				if (cmdtab[i].mi || (recent && cmdtab[i].cmd_id >= wxID_FILE1 &&
				                     cmdtab[i].cmd_id <= wxID_FILE10) ||
				        cmdtab[i].cmd_id == noop_id)
					continue;

				if (!oid.IsOk())
					oid = tc->AppendItem(rid, _("Other commands"));

				TreeInt* val = new TreeInt(i);
				tc->AppendItem(oid, cmdtab[i].name, -1, -1, val);
			}

			tc->ExpandAll();
			// FIXME: make this actually show the entire line w/o scrolling
			// BestSize cuts off on rhs; MaxSize is completely invalid
			wxSize sz = tc->GetBestSize();

			if (sz.GetHeight() > 200)
				sz.SetHeight(200);

			tc->SetSize(sz);
			sz.SetWidth(-1);  // maybe allow it to become bigger
			tc->SetSizeHints(sz, sz);
			int w, h;
			lb->GetTextExtent(wxT("CTRL-ALT-SHIFT-ENTER"), &w, &h);
			sz.Set(w, h);
			lb->SetMinSize(sz);
			sz.Set(0, 0);
			wxControl* curas = accel_config_handler.curas;

			for (int i = 0; i < ncmds; i++)
			{
				wxString labs;
				treeid_to_name(i, labs, tc, tc->GetRootItem());
				curas->GetTextExtent(labs, &w, &h);

				if (w > sz.GetWidth())
					sz.SetWidth(w);

				if (h > sz.GetHeight())
					sz.SetHeight(h);
			}

			curas->SetSize(sz);
			curas->SetSizeHints(sz);
			tc->Connect(wxEVT_COMMAND_TREE_SEL_CHANGING,
			            wxTreeEventHandler(AccelConfig_t::CommandSel),
			            NULL, &accel_config_handler);
			tc->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
			            wxTreeEventHandler(AccelConfig_t::CommandSel),
			            NULL, &accel_config_handler);
			d->Connect(wxEVT_SHOW, wxShowEventHandler(AccelConfig_t::Init),
			           NULL, &accel_config_handler);
			d->Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(AccelConfig_t::Set),
			           NULL, &accel_config_handler);
			d->Connect(XRCID("Assign"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(AccelConfig_t::Assign),
			           NULL, &accel_config_handler);
			d->Connect(XRCID("Remove"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(AccelConfig_t::Remove),
			           NULL, &accel_config_handler);
			d->Connect(XRCID("ResetAll"), wxEVT_COMMAND_BUTTON_CLICKED,
			           wxCommandEventHandler(AccelConfig_t::ResetAll),
			           NULL, &accel_config_handler);
			lb->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
			            wxCommandEventHandler(AccelConfig_t::KeySel),
			            NULL, &accel_config_handler);
			d->Connect(XRCID("Shortcut"), wxEVT_COMMAND_TEXT_UPDATED,
			           wxCommandEventHandler(AccelConfig_t::CheckKey),
			           NULL, &accel_config_handler);
			d->Fit();
		}
	}
	catch (std::exception &e)
	{
		wxLogError(wxString::FromUTF8(e.what()));
		return false;
	}

	//// Debug menu
	// actually, the viewers can be instantiated multiple times.
	// since they're for debugging, it's probably OK to just detect errors
	// at popup time.
	// The only one that can only be popped up once is logging, so allocate
	// and check it already.
	logdlg = new LogDialog;
	// activate OnDropFile event handler
#if !defined(__WXGTK__) || wxCHECK_VERSION(2,8,10)
	// may not actually do anything, but verfied to work w/ Linux/Nautilus
	DragAcceptFiles(true);
#endif

	// delayed fullscreen
	if (wxGetApp().pending_fullscreen)
		panel->ShowFullScreen(true);

	MainFrame* mf = wxGetApp().frame;

	if (gopts.statusbar)
		mf->GetStatusBar()->Show();
	else
		mf->GetStatusBar()->Hide();

	if (gopts.keep_on_top)
		mf->SetWindowStyle(mf->GetWindowStyle() | wxSTAY_ON_TOP);
	else
		mf->SetWindowStyle(mf->GetWindowStyle() & ~wxSTAY_ON_TOP);

#ifndef NO_LINK
	LinkMode linkMode = GetConfiguredLinkMode();

	if (linkMode == LINK_GAMECUBE_DOLPHIN)
	{
		bool isv = !gopts.link_host.empty();

		if (isv)
		{
			isv = SetLinkServerHost(gopts.link_host.mb_str());
		}

		if (!isv)
		{
			wxLogError(_("JoyBus host invalid; disabling"));
		}
		else
		{
			linkMode = LINK_DISCONNECTED;
		}
	}

	ConnectionState linkState = InitLink(linkMode);

	if (linkState != LINK_OK)
	{
		CloseLink();
	}

	if (GetLinkMode() != LINK_DISCONNECTED)
	{
		cmd_enable |= CMDEN_LINK_ANY;
		SetLinkTimeout(linkTimeout);
		EnableSpeedHacks(linkHacks);
	}

	EnableNetworkMenu();
#endif
	enable_menus();
	panel->SetFrameTitle();
	// All OK; activate idle loop
	panel->SetExtraStyle(panel->GetExtraStyle() | wxWS_EX_PROCESS_IDLE);
	return true;
}


